From 695ddd70dd973608734db34bc3ad49ba275f6a62 Mon Sep 17 00:00:00 2001 From: Andrey Yolkin Date: Thu, 15 Dec 2022 17:40:43 +0300 Subject: [PATCH] feat: backport `navigateTo` updates from v3 (#666) --- packages/bridge/src/runtime/composables.ts | 43 +++++++++++++++++----- playground/pages/navigate-to-external.vue | 7 ++++ playground/pages/navigate-to.vue | 2 +- test/bridge.test.ts | 13 +++++++ 4 files changed, 54 insertions(+), 11 deletions(-) create mode 100644 playground/pages/navigate-to-external.vue diff --git a/packages/bridge/src/runtime/composables.ts b/packages/bridge/src/runtime/composables.ts index 301a30b5..06298fbb 100644 --- a/packages/bridge/src/runtime/composables.ts +++ b/packages/bridge/src/runtime/composables.ts @@ -2,12 +2,12 @@ import { getCurrentInstance, onBeforeUnmount, isRef, watch, reactive, toRef, isR import type { CombinedVueInstance } from 'vue/types/vue' import type { MetaInfo } from 'vue-meta' import type VueRouter from 'vue-router' -import type { Location, RawLocation, Route } from 'vue-router' +import type { Location, RawLocation, Route, NavigationFailure } from 'vue-router' import type { RuntimeConfig } from '@nuxt/schema' import { sendRedirect } from 'h3' import { defu } from 'defu' import { useRouter as useVueRouter, useRoute as useVueRoute } from 'vue-router/composables' -import { joinURL } from 'ufo' +import { hasProtocol, joinURL, parseURL } from 'ufo' import { useNuxtApp } from './app' export { useLazyAsyncData, refreshNuxtData } from './asyncData' @@ -189,21 +189,44 @@ const isProcessingMiddleware = () => { export interface NavigateToOptions { replace?: boolean + redirectCode?: number, + external?: boolean } -export const navigateTo = (to: RawLocation, options: NavigateToOptions = {}): Promise | RawLocation | Route => { - if (isProcessingMiddleware()) { +export const navigateTo = (to: RawLocation | undefined | null, options?: NavigateToOptions): Promise | RawLocation | Route => { + if (!to) { + to = '/' + } + const toPath = typeof to === 'string' ? to : (to.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 (process.client && !isExternal && isProcessingMiddleware()) { return to } const router = useRouter() - if (process.server && useNuxtApp().ssrContext) { - // Server-side redirection using h3 res from ssrContext - const event = useNuxtApp().ssrContext?.event - const redirectLocation = joinURL(useRuntimeConfig().app.baseURL, router.resolve(to).fullPath || '/') - return sendRedirect(event, redirectLocation) + if (process.server) { + const nuxtApp = useNuxtApp() + if (nuxtApp.ssrContext && nuxtApp.ssrContext.event) { + 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)) + } } // Client-side redirection using vue-router - return options.replace ? router.replace(to) : router.push(to) + if (isExternal) { + if (options?.replace) { + location.replace(toPath) + } else { + location.href = toPath + } + return Promise.resolve() + } + return options?.replace ? router.replace(to) : router.push(to) } /** This will abort navigation within a Nuxt route middleware handler. */ diff --git a/playground/pages/navigate-to-external.vue b/playground/pages/navigate-to-external.vue new file mode 100644 index 00000000..b37df6c3 --- /dev/null +++ b/playground/pages/navigate-to-external.vue @@ -0,0 +1,7 @@ + + + diff --git a/playground/pages/navigate-to.vue b/playground/pages/navigate-to.vue index 717569e2..68db1537 100644 --- a/playground/pages/navigate-to.vue +++ b/playground/pages/navigate-to.vue @@ -3,5 +3,5 @@ diff --git a/test/bridge.test.ts b/test/bridge.test.ts index 4223cf26..66137e90 100644 --- a/test/bridge.test.ts +++ b/test/bridge.test.ts @@ -26,6 +26,19 @@ describe('navigate', () => { expect(headers.get('location')).toEqual('/') await expectNoClientErrors('/navigate-to/') }) + it('should redirect to index with navigateTo and 301 code', async () => { + const res = await fetch('/navigate-to/', { redirect: 'manual' }) + expect(res.status).toBe(301) + await expectNoClientErrors('/navigate-to/') + }) +}) + +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('legacy capi', () => {