From 15914063d256643dbb451c3062f3854abe578625 Mon Sep 17 00:00:00 2001 From: Alexandru Date: Thu, 1 Feb 2024 15:02:22 +0200 Subject: [PATCH 1/8] feat: LDP-2335: Allow passing through response headers to client --- README.md | 2 ++ src/module.ts | 8 +++++++- src/runtime/composables/useDrupalCe.ts | 12 ++++++++++-- src/runtime/passThroughHeaders.ts | 16 ++++++++++++++++ 4 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 src/runtime/passThroughHeaders.ts diff --git a/README.md b/README.md index 00d1f52c..031c6fdb 100755 --- a/README.md +++ b/README.md @@ -95,6 +95,8 @@ is added automatically to requests. Defaults to `false`. - `exposeAPIRouteRules`: If enabled, the module will create a Nitro server handler that proxies API requests to Drupal backend. Defaults to `true` for SSR (it's disabled for SSG). +- `passThroughHeaders`: Response headers to pass through from Drupal to the client. Defaults to `cache-control`. + ## Overriding options with environment variables Runtime config values can be overridden with environment variables via `NUXT_PUBLIC_` prefix. Supported runtime overrides: diff --git a/src/module.ts b/src/module.ts index f47c53c7..4f373ec6 100644 --- a/src/module.ts +++ b/src/module.ts @@ -17,6 +17,7 @@ export interface ModuleOptions { fetchProxyHeaders: string[], useLocalizedMenuEndpoint: boolean, exposeAPIRouteRules: boolean, + passThroughHeaders?: string[], } export default defineNuxtModule({ @@ -38,7 +39,8 @@ export default defineNuxtModule({ fetchProxyHeaders: ['cookie'], useLocalizedMenuEndpoint: true, addRequestFormat: false, - exposeAPIRouteRules: true + exposeAPIRouteRules: true, + passThroughHeaders: ['cache-control'] }, setup (options, nuxt) { // Keep backwards compatibility for baseURL(deprecated). @@ -83,6 +85,10 @@ export default defineNuxtModule({ handler: resolve(runtimeDir, 'server/api/menu') }) } + + if (options.passThroughHeaders) { + addPlugin(resolve(runtimeDir, 'passThroughHeaders')) + } } }) diff --git a/src/runtime/composables/useDrupalCe.ts b/src/runtime/composables/useDrupalCe.ts index 72f2bf1d..86800012 100644 --- a/src/runtime/composables/useDrupalCe.ts +++ b/src/runtime/composables/useDrupalCe.ts @@ -52,11 +52,18 @@ export const useDrupalCe = () => { jsonld: [] }, page_layout: 'default', - title: '' + title: '', + headers: {} })) + const headers = ref({}) + useFetchOptions.key = `page-${path}` useFetchOptions = processFetchOptions(useFetchOptions) useFetchOptions.query = useFetchOptions.query ?? {} + useFetchOptions.onResponse = (context) => { + const headersObject = Object.fromEntries([...context.response.headers.entries()]) + headers.value = headersObject + } if (config.addRequestContentFormat) { useFetchOptions.query._content_format = config.addRequestContentFormat @@ -83,7 +90,8 @@ export const useDrupalCe = () => { page.value?.messages && pushMessagesToState(page.value.messages) - pageState.value = page + pageState.value = page.value + pageState.value.headers = headers.value return page } diff --git a/src/runtime/passThroughHeaders.ts b/src/runtime/passThroughHeaders.ts new file mode 100644 index 00000000..cde92534 --- /dev/null +++ b/src/runtime/passThroughHeaders.ts @@ -0,0 +1,16 @@ +import { defineNuxtPlugin } from '#app' + +export default defineNuxtPlugin(async (nuxtApp) => { + const { getPage } = useDrupalCe() + const { passThroughHeaders } = useRuntimeConfig().public.drupalCe + const page = await getPage() + nuxtApp.hook('app:rendered', (ctx) => { + if (page.value.headers) { + Object.keys(page.value.headers).forEach((key) => { + if (passThroughHeaders.includes(key)) { + ctx.ssrContext?.event.node.res.setHeader(key, page.value.headers[key]) + } + }) + } + }) +}) From 1b3aae3cd727a8e2d740b660b325b62c4c6ca3c2 Mon Sep 17 00:00:00 2001 From: Alexandru Date: Mon, 5 Feb 2024 16:22:15 +0200 Subject: [PATCH 2/8] LDP-2335: Replace the plugin with a SSR composable --- playground/pages/[...slug].vue | 7 ++++- src/module.ts | 4 --- src/runtime/composables/useDrupalCe.ts | 37 +++++++++++++++++++++----- src/runtime/passThroughHeaders.ts | 16 ----------- 4 files changed, 37 insertions(+), 27 deletions(-) delete mode 100644 src/runtime/passThroughHeaders.ts diff --git a/playground/pages/[...slug].vue b/playground/pages/[...slug].vue index 3cd38634..114fd230 100644 --- a/playground/pages/[...slug].vue +++ b/playground/pages/[...slug].vue @@ -11,7 +11,7 @@ diff --git a/src/module.ts b/src/module.ts index 4f373ec6..ef55d581 100644 --- a/src/module.ts +++ b/src/module.ts @@ -85,10 +85,6 @@ export default defineNuxtModule({ handler: resolve(runtimeDir, 'server/api/menu') }) } - - if (options.passThroughHeaders) { - addPlugin(resolve(runtimeDir, 'passThroughHeaders')) - } } }) diff --git a/src/runtime/composables/useDrupalCe.ts b/src/runtime/composables/useDrupalCe.ts index 86800012..b429ac7e 100644 --- a/src/runtime/composables/useDrupalCe.ts +++ b/src/runtime/composables/useDrupalCe.ts @@ -1,5 +1,6 @@ import { callWithNuxt } from '#app' import { defu } from 'defu' +import { appendResponseHeader, H3Event } from 'h3' import { useRuntimeConfig, useRequestURL, useState, useFetch, navigateTo, createError, h, resolveComponent, setResponseStatus, useNuxtApp, useRequestHeaders, UseFetchOptions, ref, watch } from '#imports' export const useDrupalCe = () => { @@ -52,17 +53,20 @@ export const useDrupalCe = () => { jsonld: [] }, page_layout: 'default', - title: '', - headers: {} + title: '' })) + const headers = ref({}) useFetchOptions.key = `page-${path}` useFetchOptions = processFetchOptions(useFetchOptions) useFetchOptions.query = useFetchOptions.query ?? {} - useFetchOptions.onResponse = (context) => { - const headersObject = Object.fromEntries([...context.response.headers.entries()]) - headers.value = headersObject + + if (import.meta.server) { + useFetchOptions.onResponse = (context) => { + const headersObject = Object.fromEntries([...context.response.headers.entries()]) + headers.value = headersObject + } } if (config.addRequestContentFormat) { @@ -91,7 +95,9 @@ export const useDrupalCe = () => { page.value?.messages && pushMessagesToState(page.value.messages) pageState.value = page.value - pageState.value.headers = headers.value + if (import.meta.server) { + page.value.headers = headers.value + } return page } @@ -156,12 +162,31 @@ export const useDrupalCe = () => { : h(resolveComponent(customElements.element), customElements) } + /** + * Pass through headers from Drupal to the client + * @param event H3Event + * @param pageHeaders The headers from the Drupal response + * @param overridePassThroughHeaders Override/unset the current passThroughHeaders + */ + const passThroughHeaders = (event: H3Event, pageHeaders: Object, overridePassThroughHeaders?: Array) => { + const { passThroughHeaders } = useRuntimeConfig().public.drupalCe + const passThroughHeadersArray = overridePassThroughHeaders || passThroughHeaders + if (pageHeaders) { + Object.keys(pageHeaders).forEach((key) => { + if (passThroughHeadersArray.includes(key)) { + appendResponseHeader(event, key, pageHeaders[key]) + } + }) + } + } + return { fetchPage, fetchMenu, getMessages, getPage, renderCustomElements, + passThroughHeaders } } diff --git a/src/runtime/passThroughHeaders.ts b/src/runtime/passThroughHeaders.ts deleted file mode 100644 index cde92534..00000000 --- a/src/runtime/passThroughHeaders.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { defineNuxtPlugin } from '#app' - -export default defineNuxtPlugin(async (nuxtApp) => { - const { getPage } = useDrupalCe() - const { passThroughHeaders } = useRuntimeConfig().public.drupalCe - const page = await getPage() - nuxtApp.hook('app:rendered', (ctx) => { - if (page.value.headers) { - Object.keys(page.value.headers).forEach((key) => { - if (passThroughHeaders.includes(key)) { - ctx.ssrContext?.event.node.res.setHeader(key, page.value.headers[key]) - } - }) - } - }) -}) From 485703b0b6cdbf885dbb6f2b0e3f268ffb722b3d Mon Sep 17 00:00:00 2001 From: Alexandru Date: Fri, 9 Feb 2024 15:59:25 +0200 Subject: [PATCH 3/8] LDP-2335: Headers should not be sent to client --- playground/pages/[...slug].vue | 4 ++-- src/runtime/composables/useDrupalCe.ts | 13 ++++++------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/playground/pages/[...slug].vue b/playground/pages/[...slug].vue index 114fd230..c4ce449e 100644 --- a/playground/pages/[...slug].vue +++ b/playground/pages/[...slug].vue @@ -12,7 +12,7 @@ diff --git a/src/runtime/composables/useDrupalCe.ts b/src/runtime/composables/useDrupalCe.ts index b429ac7e..69d8170d 100644 --- a/src/runtime/composables/useDrupalCe.ts +++ b/src/runtime/composables/useDrupalCe.ts @@ -95,10 +95,11 @@ export const useDrupalCe = () => { page.value?.messages && pushMessagesToState(page.value.messages) pageState.value = page.value + // Headers should be available only on the server. if (import.meta.server) { - page.value.headers = headers.value + return { page, headers } } - return page + return { page } } /** @@ -166,14 +167,12 @@ export const useDrupalCe = () => { * Pass through headers from Drupal to the client * @param event H3Event * @param pageHeaders The headers from the Drupal response - * @param overridePassThroughHeaders Override/unset the current passThroughHeaders + * @param passThroughHeaders The headers to pass through */ - const passThroughHeaders = (event: H3Event, pageHeaders: Object, overridePassThroughHeaders?: Array) => { - const { passThroughHeaders } = useRuntimeConfig().public.drupalCe - const passThroughHeadersArray = overridePassThroughHeaders || passThroughHeaders + const passThroughHeaders = (event: H3Event, pageHeaders: Object, passThroughHeaders: Array) => { if (pageHeaders) { Object.keys(pageHeaders).forEach((key) => { - if (passThroughHeadersArray.includes(key)) { + if (passThroughHeaders.includes(key)) { appendResponseHeader(event, key, pageHeaders[key]) } }) From 29fe2519108be41526a698b6a2747650ddf5f70a Mon Sep 17 00:00:00 2001 From: Alexandru Date: Fri, 9 Feb 2024 16:00:40 +0200 Subject: [PATCH 4/8] LDP-2335: Page is now an object --- README.md | 2 +- test/fixtures/debug/pages/[...slug].vue | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 031c6fdb..a19fbde5 100755 --- a/README.md +++ b/README.md @@ -133,7 +133,7 @@ You have the option to override the default error handlers by using a parameter function customPageError (error: Record) { throw createError({ statusCode: error.value.statusCode, statusMessage: 'No access.', data: {}, fatal: true }) } - const page = await fetchPage(useRoute().path, { query: useRoute().query }, customPageError) + const { page } = await fetchPage(useRoute().path, { query: useRoute().query }, customPageError) ``` diff --git a/test/fixtures/debug/pages/[...slug].vue b/test/fixtures/debug/pages/[...slug].vue index 5a6c8c2f..3a9fec25 100644 --- a/test/fixtures/debug/pages/[...slug].vue +++ b/test/fixtures/debug/pages/[...slug].vue @@ -8,5 +8,5 @@ From d3a59a518dd2cfddce8167edc6243477ad338d22 Mon Sep 17 00:00:00 2001 From: Alexandru Date: Fri, 9 Feb 2024 16:01:58 +0200 Subject: [PATCH 5/8] LDP-2335: Remove config references --- README.md | 2 -- src/module.ts | 2 -- 2 files changed, 4 deletions(-) diff --git a/README.md b/README.md index a19fbde5..65bb316e 100755 --- a/README.md +++ b/README.md @@ -95,8 +95,6 @@ is added automatically to requests. Defaults to `false`. - `exposeAPIRouteRules`: If enabled, the module will create a Nitro server handler that proxies API requests to Drupal backend. Defaults to `true` for SSR (it's disabled for SSG). -- `passThroughHeaders`: Response headers to pass through from Drupal to the client. Defaults to `cache-control`. - ## Overriding options with environment variables Runtime config values can be overridden with environment variables via `NUXT_PUBLIC_` prefix. Supported runtime overrides: diff --git a/src/module.ts b/src/module.ts index ef55d581..63142783 100644 --- a/src/module.ts +++ b/src/module.ts @@ -17,7 +17,6 @@ export interface ModuleOptions { fetchProxyHeaders: string[], useLocalizedMenuEndpoint: boolean, exposeAPIRouteRules: boolean, - passThroughHeaders?: string[], } export default defineNuxtModule({ @@ -40,7 +39,6 @@ export default defineNuxtModule({ useLocalizedMenuEndpoint: true, addRequestFormat: false, exposeAPIRouteRules: true, - passThroughHeaders: ['cache-control'] }, setup (options, nuxt) { // Keep backwards compatibility for baseURL(deprecated). From be2c118f175e634afd9a378bcad0d92b0beee9f0 Mon Sep 17 00:00:00 2001 From: Alexandru Date: Tue, 13 Feb 2024 20:13:45 +0200 Subject: [PATCH 6/8] LDP-2335: Call passThroughHeaders from fetchPage --- README.md | 4 +++- playground/pages/[...slug].vue | 7 +------ src/module.ts | 2 ++ src/runtime/composables/useDrupalCe.ts | 23 ++++++++--------------- test/fixtures/debug/pages/[...slug].vue | 2 +- 5 files changed, 15 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 65bb316e..031c6fdb 100755 --- a/README.md +++ b/README.md @@ -95,6 +95,8 @@ is added automatically to requests. Defaults to `false`. - `exposeAPIRouteRules`: If enabled, the module will create a Nitro server handler that proxies API requests to Drupal backend. Defaults to `true` for SSR (it's disabled for SSG). +- `passThroughHeaders`: Response headers to pass through from Drupal to the client. Defaults to `cache-control`. + ## Overriding options with environment variables Runtime config values can be overridden with environment variables via `NUXT_PUBLIC_` prefix. Supported runtime overrides: @@ -131,7 +133,7 @@ You have the option to override the default error handlers by using a parameter function customPageError (error: Record) { throw createError({ statusCode: error.value.statusCode, statusMessage: 'No access.', data: {}, fatal: true }) } - const { page } = await fetchPage(useRoute().path, { query: useRoute().query }, customPageError) + const page = await fetchPage(useRoute().path, { query: useRoute().query }, customPageError) ``` diff --git a/playground/pages/[...slug].vue b/playground/pages/[...slug].vue index c4ce449e..7d45382e 100644 --- a/playground/pages/[...slug].vue +++ b/playground/pages/[...slug].vue @@ -12,7 +12,7 @@ diff --git a/src/module.ts b/src/module.ts index 63142783..15505143 100644 --- a/src/module.ts +++ b/src/module.ts @@ -17,6 +17,7 @@ export interface ModuleOptions { fetchProxyHeaders: string[], useLocalizedMenuEndpoint: boolean, exposeAPIRouteRules: boolean, + passThroughHeaders?: string[], } export default defineNuxtModule({ @@ -39,6 +40,7 @@ export default defineNuxtModule({ useLocalizedMenuEndpoint: true, addRequestFormat: false, exposeAPIRouteRules: true, + passThroughHeaders: ['cache-control'], }, setup (options, nuxt) { // Keep backwards compatibility for baseURL(deprecated). diff --git a/src/runtime/composables/useDrupalCe.ts b/src/runtime/composables/useDrupalCe.ts index 69d8170d..e17337c4 100644 --- a/src/runtime/composables/useDrupalCe.ts +++ b/src/runtime/composables/useDrupalCe.ts @@ -1,6 +1,6 @@ import { callWithNuxt } from '#app' import { defu } from 'defu' -import { appendResponseHeader, H3Event } from 'h3' +import { appendResponseHeader } from 'h3' import { useRuntimeConfig, useRequestURL, useState, useFetch, navigateTo, createError, h, resolveComponent, setResponseStatus, useNuxtApp, useRequestHeaders, UseFetchOptions, ref, watch } from '#imports' export const useDrupalCe = () => { @@ -56,16 +56,14 @@ export const useDrupalCe = () => { title: '' })) - const headers = ref({}) - useFetchOptions.key = `page-${path}` useFetchOptions = processFetchOptions(useFetchOptions) useFetchOptions.query = useFetchOptions.query ?? {} - if (import.meta.server) { - useFetchOptions.onResponse = (context) => { + useFetchOptions.onResponse = (context) => { + if (config.passThroughHeaders && import.meta.server) { const headersObject = Object.fromEntries([...context.response.headers.entries()]) - headers.value = headersObject + passThroughHeaders(nuxtApp, headersObject) } } @@ -95,11 +93,7 @@ export const useDrupalCe = () => { page.value?.messages && pushMessagesToState(page.value.messages) pageState.value = page.value - // Headers should be available only on the server. - if (import.meta.server) { - return { page, headers } - } - return { page } + return page } /** @@ -165,14 +159,13 @@ export const useDrupalCe = () => { /** * Pass through headers from Drupal to the client - * @param event H3Event * @param pageHeaders The headers from the Drupal response - * @param passThroughHeaders The headers to pass through */ - const passThroughHeaders = (event: H3Event, pageHeaders: Object, passThroughHeaders: Array) => { + const passThroughHeaders = (nuxtApp, pageHeaders) => { + const event = nuxtApp.ssrContext.event if (pageHeaders) { Object.keys(pageHeaders).forEach((key) => { - if (passThroughHeaders.includes(key)) { + if (config.passThroughHeaders.includes(key)) { appendResponseHeader(event, key, pageHeaders[key]) } }) diff --git a/test/fixtures/debug/pages/[...slug].vue b/test/fixtures/debug/pages/[...slug].vue index 3a9fec25..5a6c8c2f 100644 --- a/test/fixtures/debug/pages/[...slug].vue +++ b/test/fixtures/debug/pages/[...slug].vue @@ -8,5 +8,5 @@ From 0e3af6eae9c0012a11ee1a854b3142953451c27b Mon Sep 17 00:00:00 2001 From: Alexandru Date: Tue, 13 Feb 2024 20:14:53 +0200 Subject: [PATCH 7/8] LDP-2335: Remove typos --- playground/pages/[...slug].vue | 2 +- src/runtime/composables/useDrupalCe.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/playground/pages/[...slug].vue b/playground/pages/[...slug].vue index 7d45382e..3cd38634 100644 --- a/playground/pages/[...slug].vue +++ b/playground/pages/[...slug].vue @@ -11,7 +11,7 @@