Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Call server island early so it can set headers #12486

Merged
merged 1 commit into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/rotten-dodos-judge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Call server island early so it can set headers
17 changes: 16 additions & 1 deletion packages/astro/src/core/server-islands/endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -120,17 +121,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<typeof ServerIsland>) {
return ServerIsland.apply(this, args);
};
Object.assign(Component, ServerIsland);
Component.propagation = 'self';
}

return renderTemplate`${renderComponent(result, 'Component', Component, props, slots)}`;
};

Expand Down
4 changes: 2 additions & 2 deletions packages/astro/src/vite-plugin-astro-server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { IncomingMessage } from 'node:http';
import type * as vite 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';
Expand Down Expand Up @@ -168,7 +168,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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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>
20 changes: 20 additions & 0 deletions packages/astro/test/server-islands.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand All @@ -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/');
Expand Down
Loading