feat(nuxt): navigateTo supports external redirects (#5022)

Co-authored-by: Pooya Parsa <pooya@pi0.io>
This commit is contained in:
Alexander Lichter 2022-08-24 18:04:56 +02:00 committed by GitHub
parent 856c2a6fbd
commit a4dfe232f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 45 additions and 7 deletions

View File

@ -1,7 +1,7 @@
import { getCurrentInstance, inject } from 'vue' import { getCurrentInstance, inject } from 'vue'
import type { Router, RouteLocationNormalizedLoaded, NavigationGuard, RouteLocationNormalized, RouteLocationRaw, NavigationFailure } from 'vue-router' import type { Router, RouteLocationNormalizedLoaded, NavigationGuard, RouteLocationNormalized, RouteLocationRaw, NavigationFailure, RouteLocationPathRaw } from 'vue-router'
import { sendRedirect } from 'h3' import { sendRedirect } from 'h3'
import { joinURL } from 'ufo' import { hasProtocol, joinURL, parseURL } from 'ufo'
import { useNuxtApp, useRuntimeConfig } from '#app' import { useNuxtApp, useRuntimeConfig } from '#app'
export const useRouter = () => { export const useRouter = () => {
@ -58,26 +58,49 @@ const isProcessingMiddleware = () => {
export interface NavigateToOptions { export interface NavigateToOptions {
replace?: boolean replace?: boolean
redirectCode?: number redirectCode?: number,
external?: boolean
} }
export const navigateTo = (to: RouteLocationRaw | undefined | null, options: NavigateToOptions = {}): Promise<void | NavigationFailure> | RouteLocationRaw => { export const navigateTo = (to: RouteLocationRaw | undefined | null, options: NavigateToOptions = {}): Promise<void | NavigationFailure> | RouteLocationRaw => {
if (!to) { if (!to) {
to = '/' to = '/'
} }
// Early redirect on client-side since only possible option is redirectCode and not applied
if (process.client && isProcessingMiddleware()) { const toPath = typeof to === 'string' ? to : ((to as RouteLocationPathRaw).path || '/')
const isExternal = hasProtocol(toPath, true)
if (isExternal && !options.external) {
throw new Error('Navigating to external URL is not allowed by default. Use `nagivateTo (url, { external: true })`.')
}
if (isExternal && parseURL(toPath).protocol === 'script:') {
throw new Error('Cannot navigate to an URL with script protocol.')
}
// Early redirect on client-side
if (!isExternal && isProcessingMiddleware()) {
return to return to
} }
const router = useRouter() const router = useRouter()
if (process.server) { if (process.server) {
const nuxtApp = useNuxtApp() const nuxtApp = useNuxtApp()
if (nuxtApp.ssrContext && nuxtApp.ssrContext.event) { if (nuxtApp.ssrContext && nuxtApp.ssrContext.event) {
const redirectLocation = joinURL(useRuntimeConfig().app.baseURL, router.resolve(to).fullPath || '/') const redirectLocation = isExternal ? toPath : joinURL(useRuntimeConfig().app.baseURL, router.resolve(to).fullPath || '/')
return nuxtApp.callHook('app:redirected').then(() => sendRedirect(nuxtApp.ssrContext!.event, redirectLocation, options.redirectCode || 302)) return nuxtApp.callHook('app:redirected').then(() => sendRedirect(nuxtApp.ssrContext!.event, redirectLocation, options.redirectCode || 301))
} }
} }
// Client-side redirection using vue-router // Client-side redirection using vue-router
if (isExternal) {
if (options.replace) {
location.replace(toPath)
} else {
location.href = toPath
}
return Promise.resolve()
}
return options.replace ? router.replace(to) : router.push(to) return options.replace ? router.replace(to) : router.push(to)
} }

View File

@ -179,6 +179,14 @@ describe('navigate', () => {
}) })
}) })
describe('navigate external', () => {
it('should redirect to example.com', async () => {
const { headers } = await fetch('/navigate-to-external/', { redirect: 'manual' })
expect(headers.get('location')).toEqual('https://example.com/')
})
})
describe('errors', () => { describe('errors', () => {
it('should render a JSON error page', async () => { it('should render a JSON error page', async () => {
const res = await fetch('/error', { const res = await fetch('/error', {

View File

@ -0,0 +1,7 @@
<template>
<div>You should not see me</div>
</template>
<script setup>
await navigateTo('https://example.com/', { external: true, replace: true })
</script>