Reputation: 2248
I have a composable that returns a function to verify auth and refresh token within an interval of 5 seconds. Both utilizing Nuxt's useCookie
utility function to perform the logic behind it.
At first, it works. But then sometimes, when the token expired, it broke and throw me an error shown on the error screen. This is the error:
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)
I've read my code implementation many times but cannot find where the problem is. Please help me find the error.
This is the piece of codes that causing this error to be happen.
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>
Upvotes: 2
Views: 1808
Reputation: 2248
After days of debugging, it turns out that my implementation should not be put inside the layouts
file.
I don't know why.
My current solution is to create a wrapper component to be used on each page.
So now my code look like this
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>
Upvotes: 0