2023-05-11 11:39:08 +00:00
import { getCurrentInstance , hasInjectionContext , inject , onUnmounted } from 'vue'
2023-03-11 18:22:29 +00:00
import type { Ref } from 'vue'
2023-05-09 17:08:07 +00:00
import type { NavigationFailure , NavigationGuard , RouteLocationNormalized , RouteLocationPathRaw , RouteLocationRaw , Router , useRoute as _useRoute , useRouter as _useRouter } from '#vue-router'
2023-04-28 10:18:03 +00:00
import { sanitizeStatusCode } from 'h3'
2023-07-30 10:09:16 +00:00
import { hasProtocol , isScriptProtocol , joinURL , parseURL , withQuery } from 'ufo'
2023-03-11 18:22:29 +00:00
2022-09-14 09:22:03 +00:00
import { useNuxtApp , useRuntimeConfig } from '../nuxt'
2022-12-11 21:44:52 +00:00
import type { NuxtError } from './error'
2023-05-25 18:29:22 +00:00
import { createError , showError } from './error'
2022-09-14 09:22:03 +00:00
import { useState } from './state'
2022-02-21 13:03:42 +00:00
2023-03-11 18:22:29 +00:00
import type { PageMeta } from '#app'
2023-07-05 10:39:39 +00:00
import { PageRouteSymbol } from '#app/components/injections'
2023-03-11 18:22:29 +00:00
2023-05-09 17:08:07 +00:00
export const useRouter : typeof _useRouter = ( ) = > {
2022-02-21 13:03:42 +00:00
return useNuxtApp ( ) ? . $router as Router
}
2023-05-09 17:08:07 +00:00
export const useRoute : typeof _useRoute = ( ) = > {
2023-04-03 10:36:17 +00:00
if ( process . dev && isProcessingMiddleware ( ) ) {
console . warn ( '[nuxt] Calling `useRoute` within middleware may lead to misleading results. Instead, use the (to, from) arguments passed to the middleware to access the new and old routes.' )
}
2023-05-11 11:39:08 +00:00
if ( hasInjectionContext ( ) ) {
2023-07-05 10:39:39 +00:00
return inject ( PageRouteSymbol , useNuxtApp ( ) . _route )
2022-08-02 09:58:03 +00:00
}
return useNuxtApp ( ) . _route
2022-02-21 13:03:42 +00:00
}
2022-11-10 13:52:04 +00:00
export const onBeforeRouteLeave = ( guard : NavigationGuard ) = > {
const unsubscribe = useRouter ( ) . beforeEach ( ( to , from , next ) = > {
if ( to === from ) { return }
return guard ( to , from , next )
} )
onUnmounted ( unsubscribe )
}
export const onBeforeRouteUpdate = ( guard : NavigationGuard ) = > {
const unsubscribe = useRouter ( ) . beforeEach ( guard )
onUnmounted ( unsubscribe )
}
2022-02-21 13:03:42 +00:00
export interface RouteMiddleware {
( to : RouteLocationNormalized , from : RouteLocationNormalized ) : ReturnType < NavigationGuard >
}
2023-06-07 10:11:24 +00:00
/*! @__NO_SIDE_EFFECTS__ */
export function defineNuxtRouteMiddleware ( middleware : RouteMiddleware ) {
return middleware
}
2022-02-21 13:03:42 +00:00
export interface AddRouteMiddlewareOptions {
global ? : boolean
}
interface AddRouteMiddleware {
( name : string , middleware : RouteMiddleware , options? : AddRouteMiddlewareOptions ) : void
( middleware : RouteMiddleware ) : void
}
export const addRouteMiddleware : AddRouteMiddleware = ( name : string | RouteMiddleware , middleware? : RouteMiddleware , options : AddRouteMiddlewareOptions = { } ) = > {
const nuxtApp = useNuxtApp ( )
2023-03-14 10:09:50 +00:00
const global = options . global || typeof name !== 'string'
const mw = typeof name !== 'string' ? name : middleware
if ( ! mw ) {
console . warn ( '[nuxt] No route middleware passed to `addRouteMiddleware`.' , name )
return
}
if ( global ) {
nuxtApp . _middleware . global . push ( mw )
2022-02-21 13:03:42 +00:00
} else {
2023-03-14 10:09:50 +00:00
nuxtApp . _middleware . named [ name ] = mw
2022-02-21 13:03:42 +00:00
}
}
const isProcessingMiddleware = ( ) = > {
try {
if ( useNuxtApp ( ) . _processingMiddleware ) {
return true
}
} catch {
// Within an async middleware
return true
}
return false
}
2023-06-07 19:27:00 +00:00
// Conditional types, either one or other
type Without < T , U > = { [ P in Exclude < keyof T , keyof U > ] ? : never }
type XOR < T , U > = ( T | U ) extends Object ? ( Without < T , U > & U ) | ( Without < U , T > & T ) : T | U
export type OpenWindowFeatures = {
popup? : boolean
noopener? : boolean
noreferrer? : boolean
} & XOR < { width? : number } , { innerWidth? : number } >
& XOR < { height? : number } , { innerHeight? : number } >
& XOR < { left? : number } , { screenX? : number } >
& XOR < { top? : number } , { screenY? : number } >
export type OpenOptions = {
target : '_blank' | '_parent' | '_self' | '_top' | ( string & { } )
windowFeatures? : OpenWindowFeatures
}
2022-03-16 21:39:47 +00:00
export interface NavigateToOptions {
replace? : boolean
2023-06-07 19:27:00 +00:00
redirectCode? : number
2022-08-24 16:04:56 +00:00
external? : boolean
2023-06-07 19:27:00 +00:00
open? : OpenOptions
2022-03-16 21:39:47 +00:00
}
2023-04-28 10:18:03 +00:00
export const navigateTo = ( to : RouteLocationRaw | undefined | null , options? : NavigateToOptions ) : Promise < void | NavigationFailure | false > | false | void | RouteLocationRaw = > {
2022-05-05 20:46:54 +00:00
if ( ! to ) {
to = '/'
}
2022-08-24 16:04:56 +00:00
2023-06-11 21:27:02 +00:00
const toPath = typeof to === 'string' ? to : ( withQuery ( ( to as RouteLocationPathRaw ) . path || '/' , to . query || { } ) + ( to . hash || '' ) )
2023-06-07 19:27:00 +00:00
// Early open handler
if ( options ? . open ) {
if ( process . client ) {
const { target = '_blank' , windowFeatures = { } } = options . open
const features = Object . entries ( windowFeatures )
. filter ( ( [ _ , value ] ) = > value !== undefined )
. map ( ( [ feature , value ] ) = > ` ${ feature . toLowerCase ( ) } = ${ value } ` )
. join ( ', ' )
open ( toPath , target , features )
}
return Promise . resolve ( )
}
2023-03-20 17:15:01 +00:00
const isExternal = options ? . external || hasProtocol ( toPath , { acceptRelative : true } )
2023-07-30 10:09:16 +00:00
if ( isExternal ) {
if ( ! options ? . external ) {
throw new Error ( 'Navigating to an external URL is not allowed by default. Use `navigateTo(url, { external: true })`.' )
}
const protocol = parseURL ( toPath ) . protocol
if ( protocol && isScriptProtocol ( protocol ) ) {
throw new Error ( ` Cannot navigate to a URL with ' ${ protocol } ' protocol. ` )
}
2022-08-24 16:04:56 +00:00
}
2023-04-13 09:58:25 +00:00
const inMiddleware = isProcessingMiddleware ( )
2022-08-24 16:04:56 +00:00
// Early redirect on client-side
2023-04-13 09:58:25 +00:00
if ( process . client && ! isExternal && inMiddleware ) {
2022-02-21 13:03:42 +00:00
return to
}
2022-08-24 16:04:56 +00:00
2022-03-16 21:39:47 +00:00
const router = useRouter ( )
2022-08-24 16:04:56 +00:00
2023-06-14 14:37:21 +00:00
const nuxtApp = useNuxtApp ( )
2022-04-19 19:13:11 +00:00
if ( process . server ) {
2023-04-28 10:18:03 +00:00
if ( nuxtApp . ssrContext ) {
2023-04-13 09:58:25 +00:00
const fullPath = typeof to === 'string' || isExternal ? toPath : router.resolve ( to ) . fullPath || '/'
2023-04-28 10:18:03 +00:00
const location = isExternal ? toPath : joinURL ( useRuntimeConfig ( ) . app . baseURL , fullPath )
2023-06-07 12:18:50 +00:00
async function redirect ( response : any ) {
2023-04-28 10:18:03 +00:00
// TODO: consider deprecating in favour of `app:rendered` and removing
await nuxtApp . callHook ( 'app:redirected' )
const encodedLoc = location . replace ( /"/g , '%22' )
nuxtApp . ssrContext ! . _renderResponse = {
statusCode : sanitizeStatusCode ( options ? . redirectCode || 302 , 302 ) ,
body : ` <!DOCTYPE html><html><head><meta http-equiv="refresh" content="0; url= ${ encodedLoc } "></head></html> ` ,
headers : { location }
}
2023-06-07 12:18:50 +00:00
return response
2023-04-28 10:18:03 +00:00
}
2023-04-13 09:58:25 +00:00
2023-04-28 10:18:03 +00:00
// We wait to perform the redirect last in case any other middleware will intercept the redirect
// and redirect somewhere else instead.
2023-04-13 09:58:25 +00:00
if ( ! isExternal && inMiddleware ) {
2023-06-07 12:18:50 +00:00
router . afterEach ( final = > final . fullPath === fullPath ? redirect ( false ) : undefined )
2023-01-23 11:18:33 +00:00
return to
}
2023-06-07 12:18:50 +00:00
return redirect ( ! inMiddleware ? undefined : /* abort route navigation */ false )
2022-04-07 11:28:04 +00:00
}
2022-03-16 21:39:47 +00:00
}
2022-08-24 16:04:56 +00:00
2022-03-16 21:39:47 +00:00
// Client-side redirection using vue-router
2022-08-24 16:04:56 +00:00
if ( isExternal ) {
2022-09-03 12:30:03 +00:00
if ( options ? . replace ) {
2022-08-24 16:04:56 +00:00
location . replace ( toPath )
} else {
location . href = toPath
}
2023-06-14 14:37:21 +00:00
// Within in a Nuxt route middleware handler
if ( inMiddleware ) {
// Abort navigation when app is hydrated
if ( ! nuxtApp . isHydrating ) {
return false
}
// When app is hydrating (i.e. on page load), we don't want to abort navigation as
// it would lead to a 404 error / page that's blinking before location changes.
return new Promise ( ( ) = > { } )
}
2022-08-24 16:04:56 +00:00
return Promise . resolve ( )
}
2022-09-03 12:30:03 +00:00
return options ? . replace ? router . replace ( to ) : router . push ( to )
2022-02-21 13:03:42 +00:00
}
/** This will abort navigation within a Nuxt route middleware handler. */
2022-09-08 08:52:00 +00:00
export const abortNavigation = ( err? : string | Partial < NuxtError > ) = > {
2022-02-21 13:03:42 +00:00
if ( process . dev && ! isProcessingMiddleware ( ) ) {
throw new Error ( 'abortNavigation() is only usable inside a route middleware handler.' )
}
2023-05-25 18:29:22 +00:00
if ( ! err ) { return false }
err = createError ( err )
if ( err . fatal ) {
useNuxtApp ( ) . runWithContext ( ( ) = > showError ( err as NuxtError ) )
2022-02-21 13:03:42 +00:00
}
2023-05-25 18:29:22 +00:00
throw err
2022-02-21 13:03:42 +00:00
}
2022-08-31 08:02:48 +00:00
2023-07-31 08:50:55 +00:00
export const setPageLayout = ( layout : unknown extends PageMeta [ 'layout' ] ? string : PageMeta [ 'layout' ] ) = > {
2022-08-31 08:02:48 +00:00
if ( process . server ) {
2022-10-25 12:25:49 +00:00
if ( process . dev && getCurrentInstance ( ) && useState ( '_layout' ) . value !== layout ) {
console . warn ( '[warn] [nuxt] `setPageLayout` should not be called to change the layout on the server within a component as this will cause hydration errors.' )
}
2022-08-31 08:02:48 +00:00
useState ( '_layout' ) . value = layout
}
const nuxtApp = useNuxtApp ( )
2023-03-03 16:47:08 +00:00
if ( process . dev && nuxtApp . isHydrating && nuxtApp . payload . serverRendered && useState ( '_layout' ) . value !== layout ) {
2022-10-25 12:25:49 +00:00
console . warn ( '[warn] [nuxt] `setPageLayout` should not be called to change the layout during hydration as this will cause hydration errors.' )
}
2022-08-31 08:02:48 +00:00
const inMiddleware = isProcessingMiddleware ( )
if ( inMiddleware || process . server || nuxtApp . isHydrating ) {
const unsubscribe = useRouter ( ) . beforeResolve ( ( to ) = > {
2023-03-11 18:22:29 +00:00
to . meta . layout = layout as Exclude < PageMeta [ ' layout ' ] , Ref | false >
2022-08-31 08:02:48 +00:00
unsubscribe ( )
} )
}
if ( ! inMiddleware ) {
2023-03-11 18:22:29 +00:00
useRoute ( ) . meta . layout = layout as Exclude < PageMeta [ ' layout ' ] , Ref | false >
2022-08-31 08:02:48 +00:00
}
}