Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add support for custom refresh handling #715

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 8 additions & 50 deletions src/runtime/plugin.ts
Original file line number Diff line number Diff line change
@@ -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'

Expand Down Expand Up @@ -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<typeof setInterval>

// 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
Expand Down
35 changes: 30 additions & 5 deletions src/runtime/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
*/
Expand Down
79 changes: 79 additions & 0 deletions src/runtime/utils/refreshHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import type { RefreshHandlerConfig, RefreshHandler } from '../types'
import { useRuntimeConfig, useAuth, useAuthState } from '#imports'

interface DefaultRefreshHandler extends RefreshHandler {
config?: RefreshHandlerConfig
refetchIntervalTimer?: ReturnType<typeof setInterval>
refreshTokenIntervalTimer?: ReturnType<typeof setInterval>
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