我有一个可组合项,它返回一个函数以在 5 秒的间隔内验证身份验证和刷新令牌。两者都利用 Nuxt 的
useCookie
实用函数来执行其背后的逻辑。
一开始,它有效。但有时,当令牌过期时,它会损坏并向我抛出错误屏幕上显示的错误。这是错误:
500
[nuxt] A composable that requires access to the Nuxt instance was called outside of a plugin, Nuxt hook, Nuxt middleware, or Vue setup function. This is probably not a Nuxt bug. Find out more at `https://nuxt.com/docs/guide/concepts/auto-imports#using-vue-and-nuxt-composables`.
at Module.useAsyncData (./node_modules/nuxt/dist/app/composables/asyncData.js:26:38)
at useFetch (./node_modules/nuxt/dist/app/composables/fetch.js:53:43)
at refreshToken (./composables/useRepository.ts:206:35)
at verifyAuth (./composables/useRepository.ts:250:31)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async setup (./layouts/dashboard.js:35:87)
我已经多次阅读了我的代码实现,但找不到问题出在哪里。请帮我找出错误。
这是导致此错误发生的代码段。
useRepository.ts
export const useRepository = () => {
const config = useRuntimeConfig()
const cookie = useCookie<LoginResponse | null>('auth')
const refreshToken = async () => {
const { data, error } = await useFetch<RefreshTokenResponse>(
`/api/jwt-auth/jwt/refresh/`,
{
baseURL: config.public.apiUrl,
method: 'POST',
query: { format: 'json' },
body: { refresh: cookie.value?.refresh },
},
)
if (error.value) {
const { status } = error.value
if (status === 400 || status === 401 || status === 403) {
cookie.value = null
navigateTo('/login?redirect=session-expired')
return false
}
}
if (!cookie.value) {
console.warn(
'You have issued a refresh token but no users has signed in to this session, check your code implementation.',
)
return false
}
if (!data.value || !data.value.access) {
console.warn(
'Server returns no access token, users will be forcefully kicked out to the login page. Please talk to your server administrator.',
)
return false
}
cookie.value = { ...cookie.value, access: data.value.access }
return true
}
const verifyAuth = async (timer?: ReturnType<typeof setInterval>) => {
const { error, execute: refetch } = await useFetch<VerifyAuthResponse>(
`/api/jwt-auth/jwt/verify/`,
{
baseURL: config.public.apiUrl,
method: 'POST',
query: { format: 'json' },
body: { token: cookie.value?.access },
},
)
if (error.value) {
const data = await error.value.data
if (data && data.code === 'token_not_valid') {
const success = await refreshToken()
if (!success) {
if (timer) {
console.log('clearing auth cookie')
clearInterval(timer)
}
cookie.value = null
return navigateTo('/login?redirect=session-expired')
} else {
console.log('refetching')
await refetch()
}
}
}
}
return {
verifyAuth,
refreshToken,
}
}
dashboard.vue
<script setup lang="ts">
useHead({
htmlAttrs: {
lang: 'id',
},
titleTemplate(title) {
return title
? `${title} - MyApp`
: 'My Branding Punchline - MyApp'
},
bodyAttrs: {
class:
'scrollbar scrollbar-thumb-primary scrollbar-track-primary/10 scrollbar-thin scroll-smooth',
},
})
const { verifyAuth } = useRepository()
await verifyAuth()
const timer = ref<ReturnType<typeof setInterval>>()
const stopVerifyingAuth = () => {
if (timer.value) clearInterval(timer.value)
}
const continuouslyVerifyAuth = () => {
timer.value = setInterval(() => {
verifyAuth(timer.value)
}, 5000)
}
onActivated(() => continuouslyVerifyAuth())
onDeactivated(() => stopVerifyingAuth())
onUnmounted(() => stopVerifyingAuth())
const windowFocus = useWindowFocus()
watch(windowFocus, async (isFocus) => {
if (isFocus) {
await verifyAuth(timer.value)
continuouslyVerifyAuth()
} else {
stopVerifyingAuth()
}
})
</script>
<template>
<main class="box-border antialiased text-black overflow-x-hidden">
<div class="h-screen flex items-start">
<TheSidebar />
<div
class="w-full h-full overflow-y-auto scrollbar-thumb-primary scrollbar-track-primary/10 scrollbar-thin scroll-smooth"
>
<slot />
</div>
</div>
</main>
</template>
经过几天的调试,事实证明我的实现不应该放在
layouts
文件中。
我不知道为什么。
我当前的解决方案是创建一个在每个页面上使用的包装组件。
所以现在我的代码看起来像这样
layouts/dashboard.vue
<script setup lang="ts">
useHead({
htmlAttrs: {
lang: 'id',
},
titleTemplate(title) {
return title
? `${title} - MyApp`
: 'My Branding Punchline - MyApp'
},
bodyAttrs: {
class:
'scrollbar scrollbar-thumb-primary scrollbar-track-primary/10 scrollbar-thin scroll-smooth',
},
})
</script>
<template>
<main class="box-border antialiased text-black overflow-x-hidden">
<div class="h-screen flex items-start">
<TheSidebar />
<div
class="w-full h-full overflow-y-auto scrollbar-thumb-primary scrollbar-track-primary/10 scrollbar-thin scroll-smooth"
>
<slot />
</div>
</div>
</main>
</template>
components/DashboardContent.vue
<script setup lang="ts">
const { verifyAuth } = useRepository()
await verifyAuth()
const timer = ref<ReturnType<typeof setInterval>>()
const stopVerifyingAuth = () => {
if (timer.value) clearInterval(timer.value)
}
const continuouslyVerifyAuth = () => {
timer.value = setInterval(() => {
verifyAuth(timer.value)
}, 5000)
}
onActivated(() => continuouslyVerifyAuth())
onDeactivated(() => stopVerifyingAuth())
onUnmounted(() => stopVerifyingAuth())
const windowFocus = useWindowFocus()
watch(windowFocus, async (isFocus) => {
if (isFocus) {
await verifyAuth(timer.value)
continuouslyVerifyAuth()
} else {
stopVerifyingAuth()
}
})
</script>
<template>
<div>
<slot />
</div>
</template>
pages/dashboard/index.vue
<script setup lang="ts">
definePageMeta({ middleware: ['auth'], layout: 'dashboard' })
</script>
<template>
<DashboardContent>
Hello World!
</DashboardContent>
</template>