0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2024-12-30 22:03:56 -05:00

Call server island early so it can set headers (#12486)

Add changeset

Proper fix

changeup
This commit is contained in:
Matthew Phillips 2024-11-21 08:00:03 -05:00 committed by GitHub
parent a9ce785146
commit dc3d842e4c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 45 additions and 4 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Call server island early so it can set headers

View file

@ -4,6 +4,7 @@ import {
renderComponent, renderComponent,
renderTemplate, renderTemplate,
} from '../../runtime/server/index.js'; } from '../../runtime/server/index.js';
import { isAstroComponentFactory } from '../../runtime/server/render/astro/factory.js';
import { createSlotValueFromString } from '../../runtime/server/render/slot.js'; import { createSlotValueFromString } from '../../runtime/server/render/slot.js';
import type { ComponentInstance, ManifestData } from '../../types/astro.js'; import type { ComponentInstance, ManifestData } from '../../types/astro.js';
import type { RouteData, SSRManifest } from '../../types/public/internal.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 key = await manifest.key;
const encryptedProps = data.encryptedProps; const encryptedProps = data.encryptedProps;
const propString = await decryptString(key, encryptedProps); const propString = await decryptString(key, encryptedProps);
const props = JSON.parse(propString); const props = JSON.parse(propString);
const componentModule = await imp(); const componentModule = await imp();
const Component = (componentModule as any)[data.componentExport]; let Component = (componentModule as any)[data.componentExport];
const slots: ComponentSlots = {}; const slots: ComponentSlots = {};
for (const prop in data.slots) { for (const prop in data.slots) {
slots[prop] = createSlotValueFromString(data.slots[prop]); 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<typeof ServerIsland>) {
return ServerIsland.apply(this, args);
};
Object.assign(Component, ServerIsland);
Component.propagation = 'self';
}
return renderTemplate`${renderComponent(result, 'Component', Component, props, slots)}`; return renderTemplate`${renderComponent(result, 'Component', Component, props, slots)}`;
}; };

View file

@ -6,7 +6,7 @@ import type * as vite from 'vite';
import { normalizePath } from 'vite'; import { normalizePath } from 'vite';
import type { SSRManifest, SSRManifestI18n } from '../core/app/types.js'; import type { SSRManifest, SSRManifestI18n } from '../core/app/types.js';
import { warnMissingAdapter } from '../core/dev/adapter-validation.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 { getViteErrorPayload } from '../core/errors/dev/index.js';
import { AstroError, AstroErrorData } from '../core/errors/index.js'; import { AstroError, AstroErrorData } from '../core/errors/index.js';
import { patchOverlay } from '../core/errors/overlay.js'; import { patchOverlay } from '../core/errors/overlay.js';
@ -192,7 +192,7 @@ export function createDevelopmentManifest(settings: AstroSettings): SSRManifest
checkOrigin: checkOrigin:
(settings.config.security?.checkOrigin && settings.buildOutput === 'server') ?? false, (settings.config.security?.checkOrigin && settings.buildOutput === 'server') ?? false,
envGetSecretEnabled: false, envGetSecretEnabled: false,
key: createKey(), key: hasEnvironmentKey() ? getEnvironmentKey() : createKey(),
middleware() { middleware() {
return { return {
onRequest: NOOP_MIDDLEWARE_FN, onRequest: NOOP_MIDDLEWARE_FN,

View file

@ -1,4 +1,5 @@
--- ---
await new Promise(resolve => setTimeout(resolve, 1));
Astro.response.headers.set('X-Works', 'true');
--- ---
<h2 id="island">I'm an island</h2> <h2 id="island">I'm an island</h2>

View file

@ -19,11 +19,13 @@ describe('Server islands', () => {
let devServer; let devServer;
before(async () => { before(async () => {
process.env.ASTRO_KEY = 'eKBaVEuI7YjfanEXHuJe/pwZKKt3LkAHeMxvTU7aR0M=';
devServer = await fixture.startDevServer(); devServer = await fixture.startDevServer();
}); });
after(async () => { after(async () => {
await devServer.stop(); await devServer.stop();
delete process.env.ASTRO_KEY;
}); });
it('omits the islands HTML', async () => { it('omits the islands HTML', async () => {
@ -34,13 +36,31 @@ describe('Server islands', () => {
const serverIslandEl = $('h2#island'); const serverIslandEl = $('h2#island');
assert.equal(serverIslandEl.length, 0); 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', () => { describe('prod', () => {
before(async () => { before(async () => {
process.env.ASTRO_KEY = 'eKBaVEuI7YjfanEXHuJe/pwZKKt3LkAHeMxvTU7aR0M=';
await fixture.build(); await fixture.build();
}); });
after(async () => {
delete process.env.ASTRO_KEY;
});
it('omits the islands HTML', async () => { it('omits the islands HTML', async () => {
const app = await fixture.loadTestAdapterApp(); const app = await fixture.loadTestAdapterApp();
const request = new Request('http://example.com/'); const request = new Request('http://example.com/');