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'
2022-08-24 16:04:56 +00:00
import { hasProtocol , joinURL , parseURL } 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'
import { createError } 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-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 ( ) ) {
2022-08-07 04:57:11 +00:00
return inject ( '_route' , 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 >
}
export const defineNuxtRouteMiddleware = ( middleware : RouteMiddleware ) = > middleware
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
}
2022-03-16 21:39:47 +00:00
export interface NavigateToOptions {
replace? : boolean
2022-08-24 16:04:56 +00:00
redirectCode? : number ,
external? : boolean
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
const toPath = typeof to === 'string' ? to : ( ( to as RouteLocationPathRaw ) . path || '/' )
2023-03-20 17:15:01 +00:00
const isExternal = options ? . external || hasProtocol ( toPath , { acceptRelative : true } )
2022-09-03 12:30:03 +00:00
if ( isExternal && ! options ? . external ) {
2023-02-12 19:16:42 +00:00
throw new Error ( 'Navigating to external URL is not allowed by default. Use `navigateTo (url, { external: true })`.' )
2022-08-24 16:04:56 +00:00
}
if ( isExternal && parseURL ( toPath ) . protocol === 'script:' ) {
throw new Error ( 'Cannot navigate to an URL with script protocol.' )
}
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
2022-04-19 19:13:11 +00:00
if ( process . server ) {
const nuxtApp = useNuxtApp ( )
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 )
async function redirect ( ) {
// 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 }
}
return inMiddleware ? /* abort route navigation */ false : undefined
}
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-04-28 10:18:03 +00:00
router . afterEach ( final = > ( final . fullPath === fullPath ) ? redirect ( ) : undefined )
2023-01-23 11:18:33 +00:00
return to
}
2023-04-13 09:58:25 +00:00
return redirect ( )
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
}
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.' )
}
if ( err ) {
2022-09-08 08:52:00 +00:00
throw createError ( err )
2022-02-21 13:03:42 +00:00
}
return false
}
2022-08-31 08:02:48 +00:00
export const setPageLayout = ( layout : string ) = > {
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
}
}