From 76b4560b87cb8b556a4aed8c0e87b68d76d24cf2 Mon Sep 17 00:00:00 2001
From: Alexandru Teodor <videoloss3@gmail.com>
Date: Mon, 19 Feb 2024 14:17:56 +0200
Subject: [PATCH] feat: LDP-2335: pass through response headers on the server 
 (#190)

* feat: LDP-2335: Allow passing through response headers to client

* LDP-2335: Replace the plugin with a SSR composable

* LDP-2335: Headers should not be sent to client

* LDP-2335: Page is now an object

* LDP-2335: Remove config references

* LDP-2335: Call passThroughHeaders from fetchPage

* LDP-2335: Remove typos

* LDP-2335: Improve docs and add SSR context check
---
 README.md                              |  2 ++
 src/module.ts                          |  4 +++-
 src/runtime/composables/useDrupalCe.ts | 28 ++++++++++++++++++++++++++
 3 files changed, 33 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 00d1f52c..f6ec5dcc 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', 'content-language', 'set-cookie', 'x-drupal-cache', 'x-drupal-dynamic-cache']. Note: This is only available in SSR mode.
+
 ## 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..c392a421 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<ModuleOptions>({
@@ -38,7 +39,8 @@ export default defineNuxtModule<ModuleOptions>({
     fetchProxyHeaders: ['cookie'],
     useLocalizedMenuEndpoint: true,
     addRequestFormat: false,
-    exposeAPIRouteRules: true
+    exposeAPIRouteRules: true,
+    passThroughHeaders: ['cache-control', 'content-language', 'set-cookie', 'x-drupal-cache', 'x-drupal-dynamic-cache'],
   },
   setup (options, nuxt) {
     // Keep backwards compatibility for baseURL(deprecated).
diff --git a/src/runtime/composables/useDrupalCe.ts b/src/runtime/composables/useDrupalCe.ts
index 72f2bf1d..a13d741c 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 } from 'h3'
 import { useRuntimeConfig, useRequestURL, useState, useFetch, navigateTo, createError, h, resolveComponent, setResponseStatus, useNuxtApp, useRequestHeaders, UseFetchOptions, ref, watch } from '#imports'
 
 export const useDrupalCe = () => {
@@ -58,6 +59,13 @@ export const useDrupalCe = () => {
     useFetchOptions = processFetchOptions(useFetchOptions)
     useFetchOptions.query = useFetchOptions.query ?? {}
 
+    useFetchOptions.onResponse = (context) => {
+      if (config.passThroughHeaders && import.meta.server) {
+        const headersObject = Object.fromEntries([...context.response.headers.entries()])
+        passThroughHeaders(nuxtApp, headersObject)
+      }
+    }
+
     if (config.addRequestContentFormat) {
       useFetchOptions.query._content_format = config.addRequestContentFormat
     }
@@ -148,12 +156,32 @@ export const useDrupalCe = () => {
       : h(resolveComponent(customElements.element), customElements)
   }
 
+  /**
+   * Pass through headers from Drupal to the client
+   * @param pageHeaders The headers from the Drupal response
+   */
+  const passThroughHeaders = (nuxtApp, pageHeaders) => {
+    // Only run when SSR context is available.
+    if (!nuxtApp.ssrContext) {
+      return
+    }
+    const event = nuxtApp.ssrContext.event
+    if (pageHeaders) {
+      Object.keys(pageHeaders).forEach((key) => {
+        if (config.passThroughHeaders.includes(key)) {
+          appendResponseHeader(event, key, pageHeaders[key])
+        }
+      })
+    }
+  }
+
   return {
     fetchPage,
     fetchMenu,
     getMessages,
     getPage,
     renderCustomElements,
+    passThroughHeaders
   }
 }