diff --git a/src/runtime/plugin.ts b/src/runtime/plugin.ts index 819ab304..a2af46de 100644 --- a/src/runtime/plugin.ts +++ b/src/runtime/plugin.ts @@ -1,5 +1,7 @@ import { getHeader } from 'h3' import authMiddleware from './middleware/auth' +import type { RefreshHandler } from './types' +import defaultRefreshHandler from './utils/refreshHandler' import { getNitroRouteRules } from './utils/kit' import { addRouteMiddleware, defineNuxtPlugin, useRuntimeConfig, useAuth, useAuthState } from '#imports' @@ -35,65 +37,21 @@ export default defineNuxtPlugin(async (nuxtApp) => { } // 2. Setup session maintanence, e.g., auto refreshing or refreshing on foux - const { enableRefreshOnWindowFocus, enableRefreshPeriodically } = - runtimeConfig.session - - // Listen for when the page is visible, if the user switches tabs - // and makes our tab visible again, re-fetch the session, but only if - // this feature is not disabled. - const visibilityHandler = () => { - if (enableRefreshOnWindowFocus && document.visibilityState === 'visible') { - getSession() - } - } - - // Refetch interval - let refetchIntervalTimer: ReturnType - - // TODO: find more Generic method to start a Timer for the Refresh Token - // Refetch interval for local/refresh schema - let refreshTokenIntervalTimer: typeof refetchIntervalTimer + const refreshHandler: RefreshHandler = + typeof runtimeConfig.session.refreshHandler === 'undefined' + ? defaultRefreshHandler + : runtimeConfig.session.refreshHandler nuxtApp.hook('app:mounted', () => { + refreshHandler.init(runtimeConfig.session) if (disableServerSideAuth) { getSession() } - - document.addEventListener('visibilitychange', visibilityHandler, false) - - if (enableRefreshPeriodically !== false) { - const intervalTime = - enableRefreshPeriodically === true ? 1000 : enableRefreshPeriodically - refetchIntervalTimer = setInterval(() => { - if (data.value) { - getSession() - } - }, intervalTime) - } - - if (runtimeConfig.provider.type === 'refresh') { - const intervalTime = runtimeConfig.provider.token.maxAgeInSeconds! * 1000 - const { refresh, refreshToken } = useAuth() - refreshTokenIntervalTimer = setInterval(() => { - if (refreshToken.value) { - refresh() - } - }, intervalTime) - } }) const _unmount = nuxtApp.vueApp.unmount nuxtApp.vueApp.unmount = function () { - // Clear visibility handler - document.removeEventListener('visibilitychange', visibilityHandler, false) - - // Clear refetch interval - clearInterval(refetchIntervalTimer) - - // Clear refetch interval - if (refreshTokenIntervalTimer) { - clearInterval(refreshTokenIntervalTimer) - } + refreshHandler.destroy() // Clear session lastRefreshedAt.value = undefined diff --git a/src/runtime/types.ts b/src/runtime/types.ts index 0375c2c7..ee5d0454 100644 --- a/src/runtime/types.ts +++ b/src/runtime/types.ts @@ -335,11 +335,8 @@ export type AuthProviders = | ProviderLocal | ProviderLocalRefresh; -/** - * Configuration for the application-side session. - */ -type SessionConfig = { - /** +export type RefreshHandlerConfig = { + /** * Whether to refresh the session every `X` milliseconds. Set this to `false` to turn it off. The session will only be refreshed if a session already exists. * * Setting this to `true` will refresh the session every second. @@ -360,6 +357,34 @@ type SessionConfig = { enableRefreshOnWindowFocus: boolean; }; +export type RefreshHandler = { + /** + * Initializes the refresh handler with the given configuration. + * init will be called inside app:mouted lifecycle hook. + * + * @param config The configuration to use for the refresh handler. + */ + init: (config: RefreshHandlerConfig) => void; + + /** + * Handles cleanup of the refresh handler. This method will be called when the app is destroyed. + */ + destroy: () => void; +}; + +/** + * Configuration for the application-side session. + */ +type SessionConfig = RefreshHandlerConfig & { + /** + * A custom refresh handler to use. This can be used to implement custom session refresh logic. If not set, the default refresh handler will be used. + * + * @example MyCustomRefreshHandler + * @default undefined + */ + refreshHandler?: RefreshHandler; +}; + /** * Configuration for the whole module. */ diff --git a/src/runtime/utils/refreshHandler.ts b/src/runtime/utils/refreshHandler.ts new file mode 100644 index 00000000..8e4c801f --- /dev/null +++ b/src/runtime/utils/refreshHandler.ts @@ -0,0 +1,79 @@ +import type { RefreshHandlerConfig, RefreshHandler } from '../types' +import { useRuntimeConfig, useAuth, useAuthState } from '#imports' + +interface DefaultRefreshHandler extends RefreshHandler { + config?: RefreshHandlerConfig + refetchIntervalTimer?: ReturnType + refreshTokenIntervalTimer?: ReturnType + visibilityHandler(): void +} + +const defaultRefreshHandler: DefaultRefreshHandler = { + // Session configuration keep this for reference + config: undefined, + + // Refetch interval + refetchIntervalTimer: undefined, + + // TODO: find more Generic method to start a Timer for the Refresh Token + // Refetch interval for local/refresh schema + refreshTokenIntervalTimer: undefined, + + visibilityHandler () { + // Listen for when the page is visible, if the user switches tabs + // and makes our tab visible again, re-fetch the session, but only if + // this feature is not disabled. + if (this.config?.enableRefreshOnWindowFocus && document.visibilityState === 'visible') { + useAuth().getSession() + } + }, + + init (config: RefreshHandlerConfig): void { + this.config = config + + const runtimeConfig = useRuntimeConfig().public.auth + + const { data } = useAuthState() + const { getSession } = useAuth() + + document.addEventListener('visibilitychange', this.visibilityHandler, false) + + const { enableRefreshPeriodically } = config + + if (enableRefreshPeriodically !== false) { + const intervalTime = + enableRefreshPeriodically === true ? 1000 : enableRefreshPeriodically + this.refetchIntervalTimer = setInterval(() => { + if (data.value) { + getSession() + } + }, intervalTime) + } + + if (runtimeConfig.provider.type === 'refresh') { + const intervalTime = runtimeConfig.provider.token.maxAgeInSeconds! * 1000 + const { refresh, refreshToken } = useAuth() + + this.refreshTokenIntervalTimer = setInterval(() => { + if (refreshToken.value) { + refresh() + } + }, intervalTime) + } + }, + + destroy (): void { + // Clear visibility handler + document.removeEventListener('visibilitychange', this.visibilityHandler, false) + + // Clear refetch interval + clearInterval(this.refetchIntervalTimer) + + // Clear refetch interval + if (this.refreshTokenIntervalTimer) { + clearInterval(this.refreshTokenIntervalTimer) + } + } +} + +export default defaultRefreshHandler