From dc3d842e4c6f3b7e59da8a13447a1450013e10dc Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Thu, 21 Nov 2024 08:00:03 -0500 Subject: [PATCH] Call server island early so it can set headers (#12486) Add changeset Proper fix changeup --- .changeset/rotten-dodos-judge.md | 5 +++++ .../astro/src/core/server-islands/endpoint.ts | 17 +++++++++++++++- .../src/vite-plugin-astro-server/plugin.ts | 4 ++-- .../ssr/src/components/Island.astro | 3 ++- packages/astro/test/server-islands.test.js | 20 +++++++++++++++++++ 5 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 .changeset/rotten-dodos-judge.md diff --git a/.changeset/rotten-dodos-judge.md b/.changeset/rotten-dodos-judge.md new file mode 100644 index 0000000000..cb1cea275e --- /dev/null +++ b/.changeset/rotten-dodos-judge.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Call server island early so it can set headers diff --git a/packages/astro/src/core/server-islands/endpoint.ts b/packages/astro/src/core/server-islands/endpoint.ts index 999826a6f8..cac030409e 100644 --- a/packages/astro/src/core/server-islands/endpoint.ts +++ b/packages/astro/src/core/server-islands/endpoint.ts @@ -4,6 +4,7 @@ import { renderComponent, renderTemplate, } from '../../runtime/server/index.js'; +import { isAstroComponentFactory } from '../../runtime/server/render/astro/factory.js'; import { createSlotValueFromString } from '../../runtime/server/render/slot.js'; import type { ComponentInstance, ManifestData } from '../../types/astro.js'; import type { RouteData, SSRManifest } from '../../types/public/internal.js'; @@ -121,17 +122,31 @@ export function createEndpoint(manifest: SSRManifest) { const key = await manifest.key; const encryptedProps = data.encryptedProps; + const propString = await decryptString(key, encryptedProps); const props = JSON.parse(propString); const componentModule = await imp(); - const Component = (componentModule as any)[data.componentExport]; + let Component = (componentModule as any)[data.componentExport]; const slots: ComponentSlots = {}; for (const prop in data.slots) { slots[prop] = createSlotValueFromString(data.slots[prop]); } + // Wrap Astro components so we can set propagation to + // `self` which is needed to force the runtime to wait + // on the component before sending out the response headers. + // This allows the island to set headers (cookies). + if(isAstroComponentFactory(Component)) { + const ServerIsland = Component; + Component = function(this: typeof ServerIsland, ...args: Parameters) { + return ServerIsland.apply(this, args); + }; + Object.assign(Component, ServerIsland); + Component.propagation = 'self'; + } + return renderTemplate`${renderComponent(result, 'Component', Component, props, slots)}`; }; diff --git a/packages/astro/src/vite-plugin-astro-server/plugin.ts b/packages/astro/src/vite-plugin-astro-server/plugin.ts index ab90021b32..871a123160 100644 --- a/packages/astro/src/vite-plugin-astro-server/plugin.ts +++ b/packages/astro/src/vite-plugin-astro-server/plugin.ts @@ -6,7 +6,7 @@ import type * as vite from 'vite'; import { normalizePath } from 'vite'; import type { SSRManifest, SSRManifestI18n } from '../core/app/types.js'; import { warnMissingAdapter } from '../core/dev/adapter-validation.js'; -import { createKey } from '../core/encryption.js'; +import { createKey, getEnvironmentKey, hasEnvironmentKey } from '../core/encryption.js'; import { getViteErrorPayload } from '../core/errors/dev/index.js'; import { AstroError, AstroErrorData } from '../core/errors/index.js'; import { patchOverlay } from '../core/errors/overlay.js'; @@ -192,7 +192,7 @@ export function createDevelopmentManifest(settings: AstroSettings): SSRManifest checkOrigin: (settings.config.security?.checkOrigin && settings.buildOutput === 'server') ?? false, envGetSecretEnabled: false, - key: createKey(), + key: hasEnvironmentKey() ? getEnvironmentKey() : createKey(), middleware() { return { onRequest: NOOP_MIDDLEWARE_FN, diff --git a/packages/astro/test/fixtures/server-islands/ssr/src/components/Island.astro b/packages/astro/test/fixtures/server-islands/ssr/src/components/Island.astro index 49a5a87ae0..2ed0e02706 100644 --- a/packages/astro/test/fixtures/server-islands/ssr/src/components/Island.astro +++ b/packages/astro/test/fixtures/server-islands/ssr/src/components/Island.astro @@ -1,4 +1,5 @@ --- - +await new Promise(resolve => setTimeout(resolve, 1)); +Astro.response.headers.set('X-Works', 'true'); ---

I'm an island

diff --git a/packages/astro/test/server-islands.test.js b/packages/astro/test/server-islands.test.js index 5055369727..fbc4964b1e 100644 --- a/packages/astro/test/server-islands.test.js +++ b/packages/astro/test/server-islands.test.js @@ -19,11 +19,13 @@ describe('Server islands', () => { let devServer; before(async () => { + process.env.ASTRO_KEY = 'eKBaVEuI7YjfanEXHuJe/pwZKKt3LkAHeMxvTU7aR0M='; devServer = await fixture.startDevServer(); }); after(async () => { await devServer.stop(); + delete process.env.ASTRO_KEY; }); it('omits the islands HTML', async () => { @@ -34,13 +36,31 @@ describe('Server islands', () => { const serverIslandEl = $('h2#island'); assert.equal(serverIslandEl.length, 0); }); + + it('island can set headers', async () => { + const res = await fixture.fetch('/_server-islands/Island', { + method: 'POST', + body: JSON.stringify({ + componentExport: 'default', + encryptedProps: 'FC8337AF072BE5B1641501E1r8mLIhmIME1AV7UO9XmW9OLD', + slots: {}, + }) + }); + const works = res.headers.get('X-Works'); + assert.equal(works, 'true', 'able to set header from server island'); + }); }); describe('prod', () => { before(async () => { + process.env.ASTRO_KEY = 'eKBaVEuI7YjfanEXHuJe/pwZKKt3LkAHeMxvTU7aR0M='; await fixture.build(); }); + after(async () => { + delete process.env.ASTRO_KEY; + }); + it('omits the islands HTML', async () => { const app = await fixture.loadTestAdapterApp(); const request = new Request('http://example.com/');