Astro.cookies implementation (#4876)
* Astro.cookies implementation * Remove unused var * Fix build * Add a changesetp * Remove spoken-word expires
This commit is contained in:
parent
ec55745ae5
commit
d3091f89e9
32 changed files with 943 additions and 29 deletions
48
.changeset/thin-news-collect.md
Normal file
48
.changeset/thin-news-collect.md
Normal file
|
@ -0,0 +1,48 @@
|
|||
---
|
||||
'astro': minor
|
||||
'@astrojs/cloudflare': minor
|
||||
'@astrojs/deno': minor
|
||||
'@astrojs/netlify': minor
|
||||
'@astrojs/node': minor
|
||||
'@astrojs/vercel': minor
|
||||
---
|
||||
|
||||
Adds the Astro.cookies API
|
||||
|
||||
`Astro.cookies` is a new API for manipulating cookies in Astro components and API routes.
|
||||
|
||||
In Astro components, the new `Astro.cookies` object is a map-like object that allows you to get, set, delete, and check for a cookie's existence (`has`):
|
||||
|
||||
```astro
|
||||
---
|
||||
type Prefs = {
|
||||
darkMode: boolean;
|
||||
}
|
||||
|
||||
Astro.cookies.set<Prefs>('prefs', { darkMode: true }, {
|
||||
expires: '1 month'
|
||||
});
|
||||
|
||||
const prefs = Astro.cookies.get<Prefs>('prefs').json();
|
||||
---
|
||||
<body data-theme={prefs.darkMode ? 'dark' : 'light'}>
|
||||
```
|
||||
|
||||
Once you've set a cookie with Astro.cookies it will automatically be included in the outgoing response.
|
||||
|
||||
This API is also available with the same functionality in API routes:
|
||||
|
||||
```js
|
||||
export function post({ cookies }) {
|
||||
cookies.set('loggedIn', false);
|
||||
|
||||
return new Response(null, {
|
||||
status: 302,
|
||||
headers: {
|
||||
Location: '/login'
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
See [the RFC](https://github.com/withastro/rfcs/blob/main/proposals/0025-cookie-management.md) to learn more.
|
|
@ -114,6 +114,7 @@
|
|||
"boxen": "^6.2.1",
|
||||
"ci-info": "^3.3.1",
|
||||
"common-ancestor-path": "^1.0.1",
|
||||
"cookie": "^0.5.0",
|
||||
"debug": "^4.3.4",
|
||||
"diff": "^5.1.0",
|
||||
"eol": "^0.9.1",
|
||||
|
@ -161,6 +162,7 @@
|
|||
"@types/chai": "^4.3.1",
|
||||
"@types/common-ancestor-path": "^1.0.0",
|
||||
"@types/connect": "^3.4.35",
|
||||
"@types/cookie": "^0.5.1",
|
||||
"@types/debug": "^4.1.7",
|
||||
"@types/diff": "^5.0.2",
|
||||
"@types/estree": "^0.0.51",
|
||||
|
|
|
@ -14,6 +14,7 @@ import type * as vite from 'vite';
|
|||
import type { z } from 'zod';
|
||||
import type { SerializedSSRManifest } from '../core/app/types';
|
||||
import type { PageBuildData } from '../core/build/types';
|
||||
import type { AstroCookies } from '../core/cookies';
|
||||
import type { AstroConfigSchema } from '../core/config';
|
||||
import type { ViteConfigWithSSR } from '../core/create-vite';
|
||||
import type { AstroComponentFactory, Metadata } from '../runtime/server';
|
||||
|
@ -116,6 +117,10 @@ export interface AstroGlobal extends AstroGlobalPartial {
|
|||
*
|
||||
* [Astro reference](https://docs.astro.build/en/reference/api-reference/#url)
|
||||
*/
|
||||
/**
|
||||
* Utility for getting and setting cookies values.
|
||||
*/
|
||||
cookies: AstroCookies,
|
||||
url: URL;
|
||||
/** Parameters passed to a dynamic page generated using [getStaticPaths](https://docs.astro.build/en/reference/api-reference/#getstaticpaths)
|
||||
*
|
||||
|
@ -1083,6 +1088,7 @@ export interface AstroAdapter {
|
|||
type Body = string;
|
||||
|
||||
export interface APIContext {
|
||||
cookies: AstroCookies;
|
||||
params: Params;
|
||||
request: Request;
|
||||
}
|
||||
|
@ -1219,6 +1225,7 @@ export interface SSRResult {
|
|||
styles: Set<SSRElement>;
|
||||
scripts: Set<SSRElement>;
|
||||
links: Set<SSRElement>;
|
||||
cookies: AstroCookies | undefined;
|
||||
createAstro(
|
||||
Astro: AstroGlobalPartial,
|
||||
props: Record<string, any>,
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
} from '../render/ssr-element.js';
|
||||
import { matchRoute } from '../routing/match.js';
|
||||
export { deserializeManifest } from './common.js';
|
||||
import { getSetCookiesFromResponse } from '../cookies/index.js';
|
||||
|
||||
export const pagesVirtualModuleId = '@astrojs-pages-virtual-entry';
|
||||
export const resolvedPagesVirtualModuleId = '\0' + pagesVirtualModuleId;
|
||||
|
@ -116,6 +117,10 @@ export class App {
|
|||
}
|
||||
}
|
||||
|
||||
setCookieHeaders(response: Response) {
|
||||
return getSetCookiesFromResponse(response);
|
||||
}
|
||||
|
||||
async #renderPage(
|
||||
request: Request,
|
||||
routeData: RouteData,
|
||||
|
|
202
packages/astro/src/core/cookies/cookies.ts
Normal file
202
packages/astro/src/core/cookies/cookies.ts
Normal file
|
@ -0,0 +1,202 @@
|
|||
import type { CookieSerializeOptions } from 'cookie';
|
||||
import { parse, serialize } from 'cookie';
|
||||
|
||||
interface AstroCookieSetOptions {
|
||||
domain?: string;
|
||||
expires?: Date;
|
||||
httpOnly?: boolean;
|
||||
maxAge?: number;
|
||||
path?: string;
|
||||
sameSite?: boolean | 'lax' | 'none' | 'strict';
|
||||
secure?: boolean;
|
||||
}
|
||||
|
||||
interface AstroCookieDeleteOptions {
|
||||
path?: string;
|
||||
}
|
||||
|
||||
interface AstroCookieInterface {
|
||||
value: string | undefined;
|
||||
json(): Record<string, any>;
|
||||
number(): number;
|
||||
boolean(): boolean;
|
||||
}
|
||||
|
||||
interface AstroCookiesInterface {
|
||||
get(key: string): AstroCookieInterface;
|
||||
has(key: string): boolean;
|
||||
set(key: string, value: string | Record<string, any>, options?: AstroCookieSetOptions): void;
|
||||
delete(key: string, options?: AstroCookieDeleteOptions): void;
|
||||
}
|
||||
|
||||
const DELETED_EXPIRATION = new Date(0);
|
||||
const DELETED_VALUE = 'deleted';
|
||||
|
||||
class AstroCookie implements AstroCookieInterface {
|
||||
constructor(public value: string | undefined) {}
|
||||
json() {
|
||||
if(this.value === undefined) {
|
||||
throw new Error(`Cannot convert undefined to an object.`);
|
||||
}
|
||||
return JSON.parse(this.value);
|
||||
}
|
||||
number() {
|
||||
return Number(this.value);
|
||||
}
|
||||
boolean() {
|
||||
if(this.value === 'false') return false;
|
||||
if(this.value === '0') return false;
|
||||
return Boolean(this.value);
|
||||
}
|
||||
}
|
||||
|
||||
class AstroCookies implements AstroCookiesInterface {
|
||||
#request: Request;
|
||||
#requestValues: Record<string, string> | null;
|
||||
#outgoing: Map<string, [string, string, boolean]> | null;
|
||||
constructor(request: Request) {
|
||||
this.#request = request;
|
||||
this.#requestValues = null;
|
||||
this.#outgoing = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Astro.cookies.delete(key) is used to delete a cookie. Using this method will result
|
||||
* in a Set-Cookie header added to the response.
|
||||
* @param key The cookie to delete
|
||||
* @param options Options related to this deletion, such as the path of the cookie.
|
||||
*/
|
||||
delete(key: string, options?: AstroCookieDeleteOptions): void {
|
||||
const serializeOptions: CookieSerializeOptions = {
|
||||
expires: DELETED_EXPIRATION
|
||||
};
|
||||
|
||||
if(options?.path) {
|
||||
serializeOptions.path = options.path;
|
||||
}
|
||||
|
||||
// Set-Cookie: token=deleted; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT
|
||||
this.#ensureOutgoingMap().set(key, [
|
||||
DELETED_VALUE,
|
||||
serialize(key, DELETED_VALUE, serializeOptions),
|
||||
false
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Astro.cookies.get(key) is used to get a cookie value. The cookie value is read from the
|
||||
* request. If you have set a cookie via Astro.cookies.set(key, value), the value will be taken
|
||||
* from that set call, overriding any values already part of the request.
|
||||
* @param key The cookie to get.
|
||||
* @returns An object containing the cookie value as well as convenience methods for converting its value.
|
||||
*/
|
||||
get(key: string): AstroCookie {
|
||||
// Check for outgoing Set-Cookie values first
|
||||
if(this.#outgoing !== null && this.#outgoing.has(key)) {
|
||||
let [serializedValue,, isSetValue] = this.#outgoing.get(key)!;
|
||||
if(isSetValue) {
|
||||
return new AstroCookie(serializedValue);
|
||||
} else {
|
||||
return new AstroCookie(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
const values = this.#ensureParsed();
|
||||
const value = values[key];
|
||||
return new AstroCookie(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Astro.cookies.has(key) returns a boolean indicating whether this cookie is either
|
||||
* part of the initial request or set via Astro.cookies.set(key)
|
||||
* @param key The cookie to check for.
|
||||
* @returns
|
||||
*/
|
||||
has(key: string): boolean {
|
||||
if(this.#outgoing !== null && this.#outgoing.has(key)) {
|
||||
let [,,isSetValue] = this.#outgoing.get(key)!;
|
||||
return isSetValue;
|
||||
}
|
||||
const values = this.#ensureParsed();
|
||||
return !!values[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Astro.cookies.set(key, value) is used to set a cookie's value. If provided
|
||||
* an object it will be stringified via JSON.stringify(value). Additionally you
|
||||
* can provide options customizing how this cookie will be set, such as setting httpOnly
|
||||
* in order to prevent the cookie from being read in client-side JavaScript.
|
||||
* @param key The name of the cookie to set.
|
||||
* @param value A value, either a string or other primitive or an object.
|
||||
* @param options Options for the cookie, such as the path and security settings.
|
||||
*/
|
||||
set(key: string, value: string | Record<string, any>, options?: AstroCookieSetOptions): void {
|
||||
let serializedValue: string;
|
||||
if(typeof value === 'string') {
|
||||
serializedValue = value;
|
||||
} else {
|
||||
// Support stringifying JSON objects for convenience. First check that this is
|
||||
// a plain object and if it is, stringify. If not, allow support for toString() overrides.
|
||||
let toStringValue = value.toString();
|
||||
if(toStringValue === Object.prototype.toString.call(value)) {
|
||||
serializedValue = JSON.stringify(value);
|
||||
} else {
|
||||
serializedValue = toStringValue;
|
||||
}
|
||||
}
|
||||
|
||||
const serializeOptions: CookieSerializeOptions = {};
|
||||
if(options) {
|
||||
Object.assign(serializeOptions, options);
|
||||
}
|
||||
|
||||
this.#ensureOutgoingMap().set(key, [
|
||||
serializedValue,
|
||||
serialize(key, serializedValue, serializeOptions),
|
||||
true
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Astro.cookies.header() returns an iterator for the cookies that have previously
|
||||
* been set by either Astro.cookies.set() or Astro.cookies.delete().
|
||||
* This method is primarily used by adapters to set the header on outgoing responses.
|
||||
* @returns
|
||||
*/
|
||||
*headers(): Generator<string, void, unknown> {
|
||||
if(this.#outgoing == null) return;
|
||||
for(const [,value] of this.#outgoing) {
|
||||
yield value[1];
|
||||
}
|
||||
}
|
||||
|
||||
#ensureParsed(): Record<string, string> {
|
||||
if(!this.#requestValues) {
|
||||
this.#parse();
|
||||
}
|
||||
if(!this.#requestValues) {
|
||||
this.#requestValues = {};
|
||||
}
|
||||
return this.#requestValues;
|
||||
}
|
||||
|
||||
#ensureOutgoingMap(): Map<string, [string, string, boolean]> {
|
||||
if(!this.#outgoing) {
|
||||
this.#outgoing = new Map();
|
||||
}
|
||||
return this.#outgoing;
|
||||
}
|
||||
|
||||
#parse() {
|
||||
const raw = this.#request.headers.get('cookie');
|
||||
if(!raw) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#requestValues = parse(raw);
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
AstroCookies
|
||||
};
|
9
packages/astro/src/core/cookies/index.ts
Normal file
9
packages/astro/src/core/cookies/index.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
|
||||
export {
|
||||
AstroCookies
|
||||
} from './cookies.js';
|
||||
|
||||
export {
|
||||
attachToResponse,
|
||||
getSetCookiesFromResponse
|
||||
} from './response.js';
|
26
packages/astro/src/core/cookies/response.ts
Normal file
26
packages/astro/src/core/cookies/response.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
import type { AstroCookies } from './cookies';
|
||||
|
||||
const astroCookiesSymbol = Symbol.for('astro.cookies');
|
||||
|
||||
export function attachToResponse(response: Response, cookies: AstroCookies) {
|
||||
Reflect.set(response, astroCookiesSymbol, cookies);
|
||||
}
|
||||
|
||||
function getFromResponse(response: Response): AstroCookies | undefined {
|
||||
let cookies = Reflect.get(response, astroCookiesSymbol);
|
||||
if(cookies != null) {
|
||||
return cookies as AstroCookies;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function * getSetCookiesFromResponse(response: Response): Generator<string, void, unknown> {
|
||||
const cookies = getFromResponse(response);
|
||||
if(!cookies) {
|
||||
return;
|
||||
}
|
||||
for(const headerValue of cookies.headers()) {
|
||||
yield headerValue;
|
||||
}
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
import type { EndpointHandler } from '../../@types/astro';
|
||||
import { renderEndpoint } from '../../runtime/server/index.js';
|
||||
import type { APIContext, EndpointHandler, Params } from '../../@types/astro';
|
||||
import type { RenderOptions } from '../render/core';
|
||||
|
||||
import { AstroCookies, attachToResponse } from '../cookies/index.js';
|
||||
import { renderEndpoint } from '../../runtime/server/index.js';
|
||||
import { getParamsAndProps, GetParamsAndPropsError } from '../render/core.js';
|
||||
|
||||
export type EndpointOptions = Pick<
|
||||
|
@ -28,6 +30,14 @@ type EndpointCallResult =
|
|||
response: Response;
|
||||
};
|
||||
|
||||
function createAPIContext(request: Request, params: Params): APIContext {
|
||||
return {
|
||||
cookies: new AstroCookies(request),
|
||||
request,
|
||||
params
|
||||
};
|
||||
}
|
||||
|
||||
export async function call(
|
||||
mod: EndpointHandler,
|
||||
opts: EndpointOptions
|
||||
|
@ -41,9 +51,11 @@ export async function call(
|
|||
}
|
||||
const [params] = paramsAndPropsResp;
|
||||
|
||||
const response = await renderEndpoint(mod, opts.request, params, opts.ssr);
|
||||
const context = createAPIContext(opts.request, params);
|
||||
const response = await renderEndpoint(mod, context, opts.ssr);
|
||||
|
||||
if (response instanceof Response) {
|
||||
attachToResponse(response, context.cookies);
|
||||
return {
|
||||
type: 'response',
|
||||
response,
|
||||
|
|
|
@ -10,6 +10,7 @@ import type {
|
|||
} from '../../@types/astro';
|
||||
import type { LogOptions } from '../logger/core.js';
|
||||
|
||||
import { attachToResponse } from '../cookies/index.js';
|
||||
import { Fragment, renderPage } from '../../runtime/server/index.js';
|
||||
import { getParams } from '../routing/params.js';
|
||||
import { createResult } from './result.js';
|
||||
|
@ -164,5 +165,13 @@ export async function render(opts: RenderOptions): Promise<Response> {
|
|||
});
|
||||
}
|
||||
|
||||
return await renderPage(result, Component, pageProps, null, streaming);
|
||||
const response = await renderPage(result, Component, pageProps, null, streaming);
|
||||
|
||||
// If there is an Astro.cookies instance, attach it to the response so that
|
||||
// adapters can grab the Set-Cookie headers.
|
||||
if(result.cookies) {
|
||||
attachToResponse(response, result.cookies);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import type {
|
|||
SSRResult,
|
||||
} from '../../@types/astro';
|
||||
import { renderSlot } from '../../runtime/server/index.js';
|
||||
import { AstroCookies } from '../cookies/index.js';
|
||||
import { LogOptions, warn } from '../logger/core.js';
|
||||
import { isScriptRequest } from './script.js';
|
||||
import { isCSSRequest } from './util.js';
|
||||
|
@ -139,6 +140,9 @@ export function createResult(args: CreateResultArgs): SSRResult {
|
|||
writable: false,
|
||||
});
|
||||
|
||||
// Astro.cookies is defined lazily to avoid the cost on pages that do not use it.
|
||||
let cookies: AstroCookies | undefined = undefined;
|
||||
|
||||
// Create the result object that will be passed into the render function.
|
||||
// This object starts here as an empty shell (not yet the result) but then
|
||||
// calling the render() function will populate the object with scripts, styles, etc.
|
||||
|
@ -146,6 +150,7 @@ export function createResult(args: CreateResultArgs): SSRResult {
|
|||
styles: args.styles ?? new Set<SSRElement>(),
|
||||
scripts: args.scripts ?? new Set<SSRElement>(),
|
||||
links: args.links ?? new Set<SSRElement>(),
|
||||
cookies,
|
||||
/** This function returns the `Astro` faux-global */
|
||||
createAstro(
|
||||
astroGlobal: AstroGlobalPartial,
|
||||
|
@ -171,6 +176,14 @@ export function createResult(args: CreateResultArgs): SSRResult {
|
|||
|
||||
return Reflect.get(request, clientAddressSymbol);
|
||||
},
|
||||
get cookies() {
|
||||
if(cookies) {
|
||||
return cookies;
|
||||
}
|
||||
cookies = new AstroCookies(request);
|
||||
result.cookies = cookies;
|
||||
return cookies;
|
||||
},
|
||||
params,
|
||||
props,
|
||||
request,
|
||||
|
|
|
@ -18,12 +18,8 @@ function getHandlerFromModule(mod: EndpointHandler, method: string) {
|
|||
}
|
||||
|
||||
/** Renders an endpoint request to completion, returning the body. */
|
||||
export async function renderEndpoint(
|
||||
mod: EndpointHandler,
|
||||
request: Request,
|
||||
params: Params,
|
||||
ssr?: boolean
|
||||
) {
|
||||
export async function renderEndpoint(mod: EndpointHandler, context: APIContext, ssr: boolean) {
|
||||
const { request, params } = context;
|
||||
const chosenMethod = request.method?.toLowerCase();
|
||||
const handler = getHandlerFromModule(mod, chosenMethod);
|
||||
if (!ssr && ssr === false && chosenMethod && chosenMethod !== 'get') {
|
||||
|
@ -56,11 +52,6 @@ export function get({ params, request }) {
|
|||
Update your code to remove this warning.`);
|
||||
}
|
||||
|
||||
const context = {
|
||||
request,
|
||||
params,
|
||||
};
|
||||
|
||||
const proxy = new Proxy(context, {
|
||||
get(target, prop) {
|
||||
if (prop in target) {
|
||||
|
|
|
@ -6,6 +6,7 @@ import type { SSROptions } from '../core/render/dev/index';
|
|||
|
||||
import { Readable } from 'stream';
|
||||
import { call as callEndpoint } from '../core/endpoint/dev/index.js';
|
||||
import { getSetCookiesFromResponse } from '../core/cookies/index.js';
|
||||
import {
|
||||
collectErrorMetadata,
|
||||
ErrorWithMetadata,
|
||||
|
@ -62,6 +63,11 @@ async function writeWebResponse(res: http.ServerResponse, webResponse: Response)
|
|||
_headers = Object.fromEntries(headers.entries());
|
||||
}
|
||||
|
||||
// Attach any set-cookie headers added via Astro.cookies.set()
|
||||
const setCookieHeaders = Array.from(getSetCookiesFromResponse(webResponse));
|
||||
if(setCookieHeaders.length) {
|
||||
res.setHeader('Set-Cookie', setCookieHeaders);
|
||||
}
|
||||
res.writeHead(status, _headers);
|
||||
if (body) {
|
||||
if (Symbol.for('astro.responseBody') in webResponse) {
|
||||
|
|
119
packages/astro/test/astro-cookies.test.js
Normal file
119
packages/astro/test/astro-cookies.test.js
Normal file
|
@ -0,0 +1,119 @@
|
|||
import { expect } from 'chai';
|
||||
import * as cheerio from 'cheerio';
|
||||
import { loadFixture } from './test-utils.js';
|
||||
import testAdapter from './test-adapter.js';
|
||||
|
||||
describe('Astro.cookies', () => {
|
||||
/** @type {import('./test-utils').Fixture} */
|
||||
let fixture;
|
||||
|
||||
before(async () => {
|
||||
fixture = await loadFixture({
|
||||
root: './fixtures/astro-cookies/',
|
||||
output: 'server',
|
||||
adapter: testAdapter(),
|
||||
});
|
||||
});
|
||||
|
||||
describe('Development', () => {
|
||||
/** @type {import('./test-utils').DevServer} */
|
||||
let devServer;
|
||||
|
||||
before(async () => {
|
||||
devServer = await fixture.startDevServer();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await devServer.stop();
|
||||
});
|
||||
|
||||
it('is able to get cookies from the request', async () => {
|
||||
const response = await fixture.fetch('/get-json', {
|
||||
headers: {
|
||||
cookie: `prefs=${encodeURIComponent(JSON.stringify({ mode: 'light' }))}`
|
||||
}
|
||||
});
|
||||
expect(response.status).to.equal(200);
|
||||
const html = await response.text();
|
||||
|
||||
const $ = cheerio.load(html);
|
||||
expect($('dd').text()).to.equal('light');
|
||||
});
|
||||
|
||||
it('can set the cookie value', async () => {
|
||||
const response = await fixture.fetch('/set-value', {
|
||||
method: 'POST'
|
||||
});
|
||||
expect(response.status).to.equal(200);
|
||||
expect(response.headers.has('set-cookie')).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Production', () => {
|
||||
let app;
|
||||
before(async () => {
|
||||
await fixture.build();
|
||||
app = await fixture.loadTestAdapterApp();
|
||||
});
|
||||
|
||||
async function fetchResponse(path, requestInit) {
|
||||
const request = new Request('http://example.com' + path, requestInit);
|
||||
const response = await app.render(request);
|
||||
return response;
|
||||
}
|
||||
|
||||
it('is able to get cookies from the request', async () => {
|
||||
const response = await fetchResponse('/get-json', {
|
||||
headers: {
|
||||
cookie: `prefs=${encodeURIComponent(JSON.stringify({ mode: 'light' }))}`
|
||||
}
|
||||
});
|
||||
expect(response.status).to.equal(200);
|
||||
const html = await response.text();
|
||||
|
||||
const $ = cheerio.load(html);
|
||||
expect($('dd').text()).to.equal('light');
|
||||
});
|
||||
|
||||
it('can set the cookie value', async () => {
|
||||
const response = await fetchResponse('/set-value', {
|
||||
method: 'POST'
|
||||
});
|
||||
expect(response.status).to.equal(200);
|
||||
let headers = Array.from(app.setCookieHeaders(response));
|
||||
expect(headers).to.have.a.lengthOf(1);
|
||||
expect(headers[0]).to.match(/Expires/);
|
||||
});
|
||||
|
||||
it('Early returning a Response still includes set headers', async () => {
|
||||
const response = await fetchResponse('/early-return', {
|
||||
headers: {
|
||||
cookie: `prefs=${encodeURIComponent(JSON.stringify({ mode: 'light' }))}`
|
||||
}
|
||||
});
|
||||
expect(response.status).to.equal(302);
|
||||
let headers = Array.from(app.setCookieHeaders(response));
|
||||
expect(headers).to.have.a.lengthOf(1);
|
||||
let raw = headers[0].slice(6);
|
||||
let data = JSON.parse(decodeURIComponent(raw));
|
||||
expect(data).to.be.an('object');
|
||||
expect(data.mode).to.equal('dark');
|
||||
});
|
||||
|
||||
it('API route can get and set cookies', async () => {
|
||||
const response = await fetchResponse('/set-prefs', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
cookie: `prefs=${encodeURIComponent(JSON.stringify({ mode: 'light' }))}`
|
||||
}
|
||||
});
|
||||
expect(response.status).to.equal(302);
|
||||
let headers = Array.from(app.setCookieHeaders(response));
|
||||
expect(headers).to.have.a.lengthOf(1);
|
||||
let raw = headers[0].slice(6);
|
||||
let data = JSON.parse(decodeURIComponent(raw));
|
||||
expect(data).to.be.an('object');
|
||||
expect(data.mode).to.equal('dark');
|
||||
});
|
||||
})
|
||||
});
|
8
packages/astro/test/fixtures/astro-cookies/package.json
vendored
Normal file
8
packages/astro/test/fixtures/astro-cookies/package.json
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "@test/astro-cookies",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"astro": "workspace:*"
|
||||
}
|
||||
}
|
14
packages/astro/test/fixtures/astro-cookies/src/pages/early-return.astro
vendored
Normal file
14
packages/astro/test/fixtures/astro-cookies/src/pages/early-return.astro
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
const mode = Astro.cookies.get('prefs').json().mode;
|
||||
|
||||
Astro.cookies.set('prefs', {
|
||||
mode: mode === 'light' ? 'dark' : 'light'
|
||||
});
|
||||
|
||||
return new Response(null, {
|
||||
status: 302,
|
||||
headers: {
|
||||
'Location': '/prefs'
|
||||
}
|
||||
})
|
||||
---
|
17
packages/astro/test/fixtures/astro-cookies/src/pages/get-json.astro
vendored
Normal file
17
packages/astro/test/fixtures/astro-cookies/src/pages/get-json.astro
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
const cookie = Astro.cookies.get('prefs');
|
||||
const prefs = cookie.json();
|
||||
---
|
||||
<html>
|
||||
<head>
|
||||
<title>Testing</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Testing</h1>
|
||||
<h2>Preferences</h2>
|
||||
<dl>
|
||||
<dt>Dark/light mode</dt>
|
||||
<dd>{ prefs.mode }</dd>
|
||||
</dl>
|
||||
</body>
|
||||
</html>
|
15
packages/astro/test/fixtures/astro-cookies/src/pages/set-prefs.js
vendored
Normal file
15
packages/astro/test/fixtures/astro-cookies/src/pages/set-prefs.js
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
|
||||
export function post({ cookies }) {
|
||||
const mode = cookies.get('prefs').json().mode;
|
||||
|
||||
cookies.set('prefs', {
|
||||
mode: mode === 'light' ? 'dark' : 'light'
|
||||
});
|
||||
|
||||
return new Response(null, {
|
||||
status: 302,
|
||||
headers: {
|
||||
'Location': '/prefs'
|
||||
}
|
||||
});
|
||||
}
|
15
packages/astro/test/fixtures/astro-cookies/src/pages/set-value.astro
vendored
Normal file
15
packages/astro/test/fixtures/astro-cookies/src/pages/set-value.astro
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
if(Astro.request.method === 'POST') {
|
||||
Astro.cookies.set('admin', 'true', {
|
||||
expires: new Date()
|
||||
});
|
||||
}
|
||||
---
|
||||
<html>
|
||||
<head>
|
||||
<title>Testing</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Testing</h1>
|
||||
</body>
|
||||
</html>
|
60
packages/astro/test/units/cookies/delete.test.js
Normal file
60
packages/astro/test/units/cookies/delete.test.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
import { expect } from 'chai';
|
||||
import { AstroCookies } from '../../../dist/core/cookies/index.js';
|
||||
import { apply as applyPolyfill } from '../../../dist/core/polyfill.js';
|
||||
|
||||
applyPolyfill();
|
||||
|
||||
describe('astro/src/core/cookies', () => {
|
||||
describe('Astro.cookies.delete', () => {
|
||||
it('creates a Set-Cookie header to delete it', () => {
|
||||
let req = new Request('http://example.com/', {
|
||||
headers: {
|
||||
'cookie': 'foo=bar'
|
||||
}
|
||||
});
|
||||
let cookies = new AstroCookies(req);
|
||||
expect(cookies.get('foo').value).to.equal('bar');
|
||||
|
||||
cookies.delete('foo');
|
||||
let headers = Array.from(cookies.headers());
|
||||
expect(headers).to.have.a.lengthOf(1);
|
||||
});
|
||||
|
||||
it('calling cookies.get() after returns undefined', () => {
|
||||
let req = new Request('http://example.com/', {
|
||||
headers: {
|
||||
'cookie': 'foo=bar'
|
||||
}
|
||||
});
|
||||
let cookies = new AstroCookies(req);
|
||||
expect(cookies.get('foo').value).to.equal('bar');
|
||||
|
||||
cookies.delete('foo');
|
||||
expect(cookies.get('foo').value).to.equal(undefined);
|
||||
});
|
||||
|
||||
it('calling cookies.has() after returns false', () => {
|
||||
let req = new Request('http://example.com/', {
|
||||
headers: {
|
||||
'cookie': 'foo=bar'
|
||||
}
|
||||
});
|
||||
let cookies = new AstroCookies(req);
|
||||
expect(cookies.has('foo')).to.equal(true);
|
||||
|
||||
cookies.delete('foo');
|
||||
expect(cookies.has('foo')).to.equal(false);
|
||||
});
|
||||
|
||||
it('can provide a path', () => {
|
||||
let req = new Request('http://example.com/');
|
||||
let cookies = new AstroCookies(req);
|
||||
cookies.delete('foo', {
|
||||
path: '/subpath/'
|
||||
});
|
||||
let headers = Array.from(cookies.headers());
|
||||
expect(headers).to.have.a.lengthOf(1);
|
||||
expect(headers[0]).to.match(/Path=\/subpath\//);
|
||||
});
|
||||
});
|
||||
});
|
136
packages/astro/test/units/cookies/get.test.js
Normal file
136
packages/astro/test/units/cookies/get.test.js
Normal file
|
@ -0,0 +1,136 @@
|
|||
import { expect } from 'chai';
|
||||
import { AstroCookies } from '../../../dist/core/cookies/index.js';
|
||||
import { apply as applyPolyfill } from '../../../dist/core/polyfill.js';
|
||||
|
||||
applyPolyfill();
|
||||
|
||||
describe('astro/src/core/cookies', () => {
|
||||
describe('Astro.cookies.get', () => {
|
||||
it('gets the cookie value', () => {
|
||||
const req = new Request('http://example.com/', {
|
||||
headers: {
|
||||
'cookie': 'foo=bar'
|
||||
}
|
||||
});
|
||||
const cookies = new AstroCookies(req);
|
||||
expect(cookies.get('foo').value).to.equal('bar');
|
||||
});
|
||||
|
||||
describe('.json()', () => {
|
||||
it('returns a JavaScript object', () => {
|
||||
const req = new Request('http://example.com/', {
|
||||
headers: {
|
||||
'cookie': 'foo=%7B%22key%22%3A%22value%22%7D'
|
||||
}
|
||||
});
|
||||
let cookies = new AstroCookies(req);
|
||||
|
||||
const json = cookies.get('foo').json();
|
||||
expect(json).to.be.an('object');
|
||||
expect(json.key).to.equal('value');
|
||||
});
|
||||
|
||||
it('throws if the value is undefined', () => {
|
||||
const req = new Request('http://example.com/');
|
||||
let cookies = new AstroCookies(req);
|
||||
let cookie = cookies.get('foo');
|
||||
expect(() => cookie.json()).to.throw('Cannot convert undefined to an object.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('.number()', () => {
|
||||
it('Coerces into a number', () => {
|
||||
const req = new Request('http://example.com/', {
|
||||
headers: {
|
||||
'cookie': 'foo=22'
|
||||
}
|
||||
});
|
||||
let cookies = new AstroCookies(req);
|
||||
|
||||
const value = cookies.get('foo').number();
|
||||
expect(value).to.be.an('number');
|
||||
expect(value).to.equal(22);
|
||||
});
|
||||
|
||||
it('Coerces non-number into NaN', () => {
|
||||
const req = new Request('http://example.com/', {
|
||||
headers: {
|
||||
'cookie': 'foo=bar'
|
||||
}
|
||||
});
|
||||
let cookies = new AstroCookies(req);
|
||||
|
||||
const value = cookies.get('foo').number();
|
||||
expect(value).to.be.an('number');
|
||||
expect(Number.isNaN(value)).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('.boolean()', () => {
|
||||
it('Coerces true into `true`', () => {
|
||||
const req = new Request('http://example.com/', {
|
||||
headers: {
|
||||
'cookie': 'foo=true'
|
||||
}
|
||||
});
|
||||
let cookies = new AstroCookies(req);
|
||||
|
||||
const value = cookies.get('foo').boolean();
|
||||
expect(value).to.be.an('boolean');
|
||||
expect(value).to.equal(true);
|
||||
});
|
||||
|
||||
it('Coerces false into `false`', () => {
|
||||
const req = new Request('http://example.com/', {
|
||||
headers: {
|
||||
'cookie': 'foo=false'
|
||||
}
|
||||
});
|
||||
let cookies = new AstroCookies(req);
|
||||
|
||||
const value = cookies.get('foo').boolean();
|
||||
expect(value).to.be.an('boolean');
|
||||
expect(value).to.equal(false);
|
||||
});
|
||||
|
||||
it('Coerces 1 into `true`', () => {
|
||||
const req = new Request('http://example.com/', {
|
||||
headers: {
|
||||
'cookie': 'foo=1'
|
||||
}
|
||||
});
|
||||
let cookies = new AstroCookies(req);
|
||||
|
||||
const value = cookies.get('foo').boolean();
|
||||
expect(value).to.be.an('boolean');
|
||||
expect(value).to.equal(true);
|
||||
});
|
||||
|
||||
it('Coerces 0 into `false`', () => {
|
||||
const req = new Request('http://example.com/', {
|
||||
headers: {
|
||||
'cookie': 'foo=0'
|
||||
}
|
||||
});
|
||||
let cookies = new AstroCookies(req);
|
||||
|
||||
const value = cookies.get('foo').boolean();
|
||||
expect(value).to.be.an('boolean');
|
||||
expect(value).to.equal(false);
|
||||
});
|
||||
|
||||
it('Coerces truthy strings into `true`', () => {
|
||||
const req = new Request('http://example.com/', {
|
||||
headers: {
|
||||
'cookie': 'foo=bar'
|
||||
}
|
||||
});
|
||||
let cookies = new AstroCookies(req);
|
||||
|
||||
const value = cookies.get('foo').boolean();
|
||||
expect(value).to.be.an('boolean');
|
||||
expect(value).to.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
32
packages/astro/test/units/cookies/has.test.js
Normal file
32
packages/astro/test/units/cookies/has.test.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { expect } from 'chai';
|
||||
import { AstroCookies } from '../../../dist/core/cookies/index.js';
|
||||
import { apply as applyPolyfill } from '../../../dist/core/polyfill.js';
|
||||
|
||||
applyPolyfill();
|
||||
|
||||
describe('astro/src/core/cookies', () => {
|
||||
describe('Astro.cookies.has', () => {
|
||||
it('returns true if the request has the cookie', () => {
|
||||
let req = new Request('http://example.com/', {
|
||||
headers: {
|
||||
'cookie': 'foo=bar'
|
||||
}
|
||||
});
|
||||
let cookies = new AstroCookies(req);
|
||||
expect(cookies.has('foo')).to.equal(true);
|
||||
});
|
||||
|
||||
it('returns false if the request does not have the cookie', () => {
|
||||
let req = new Request('http://example.com/');
|
||||
let cookies = new AstroCookies(req);
|
||||
expect(cookies.has('foo')).to.equal(false);
|
||||
});
|
||||
|
||||
it('returns true if the cookie has been set', () => {
|
||||
let req = new Request('http://example.com/');
|
||||
let cookies = new AstroCookies(req);
|
||||
cookies.set('foo', 'bar');
|
||||
expect(cookies.has('foo')).to.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
82
packages/astro/test/units/cookies/set.test.js
Normal file
82
packages/astro/test/units/cookies/set.test.js
Normal file
|
@ -0,0 +1,82 @@
|
|||
import { expect } from 'chai';
|
||||
import { AstroCookies } from '../../../dist/core/cookies/index.js';
|
||||
import { apply as applyPolyfill } from '../../../dist/core/polyfill.js';
|
||||
|
||||
applyPolyfill();
|
||||
|
||||
describe('astro/src/core/cookies', () => {
|
||||
describe('Astro.cookies.set', () => {
|
||||
it('Sets a cookie value that can be serialized', () => {
|
||||
let req = new Request('http://example.com/');
|
||||
let cookies = new AstroCookies(req);
|
||||
cookies.set('foo', 'bar');
|
||||
let headers = Array.from(cookies.headers());
|
||||
expect(headers).to.have.a.lengthOf(1);
|
||||
expect(headers[0]).to.equal('foo=bar');
|
||||
});
|
||||
|
||||
it('Can set cookie options', () => {
|
||||
let req = new Request('http://example.com/');
|
||||
let cookies = new AstroCookies(req);
|
||||
cookies.set('foo', 'bar', {
|
||||
httpOnly: true,
|
||||
path: '/subpath/'
|
||||
});
|
||||
let headers = Array.from(cookies.headers());
|
||||
expect(headers).to.have.a.lengthOf(1);
|
||||
expect(headers[0]).to.equal('foo=bar; Path=/subpath/; HttpOnly');
|
||||
});
|
||||
|
||||
it('Can pass a JavaScript object that will be serialized', () => {
|
||||
let req = new Request('http://example.com/');
|
||||
let cookies = new AstroCookies(req);
|
||||
cookies.set('options', { one: 'two', three: 4 });
|
||||
let headers = Array.from(cookies.headers());
|
||||
expect(headers).to.have.a.lengthOf(1);
|
||||
expect(JSON.parse(decodeURIComponent(headers[0].slice(8))).one).to.equal('two');
|
||||
});
|
||||
|
||||
it('Can pass a number', () => {
|
||||
let req = new Request('http://example.com/');
|
||||
let cookies = new AstroCookies(req);
|
||||
cookies.set('one', 2);
|
||||
let headers = Array.from(cookies.headers());
|
||||
expect(headers).to.have.a.lengthOf(1);
|
||||
expect(headers[0]).to.equal('one=2');
|
||||
});
|
||||
|
||||
it('Can get the value after setting', () => {
|
||||
let req = new Request('http://example.com/');
|
||||
let cookies = new AstroCookies(req);
|
||||
cookies.set('foo', 'bar');
|
||||
let r = cookies.get('foo');
|
||||
expect(r.value).to.equal('bar');
|
||||
});
|
||||
|
||||
it('Can get the JavaScript object after setting', () => {
|
||||
let req = new Request('http://example.com/');
|
||||
let cookies = new AstroCookies(req);
|
||||
cookies.set('options', { one: 'two', three: 4 });
|
||||
let cook = cookies.get('options');
|
||||
let value = cook.json();
|
||||
expect(value).to.be.an('object');
|
||||
expect(value.one).to.equal('two');
|
||||
expect(value.three).to.be.a('number');
|
||||
expect(value.three).to.equal(4);
|
||||
});
|
||||
|
||||
it('Overrides a value in the request', () => {
|
||||
let req = new Request('http://example.com/', {
|
||||
headers: {
|
||||
'cookie': 'foo=bar'
|
||||
}
|
||||
});
|
||||
let cookies = new AstroCookies(req);
|
||||
expect(cookies.get('foo').value).to.equal('bar');
|
||||
|
||||
// Set a new value
|
||||
cookies.set('foo', 'baz');
|
||||
expect(cookies.get('foo').value).to.equal('baz');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -26,7 +26,15 @@ export function createExports(manifest: SSRManifest) {
|
|||
Symbol.for('astro.clientAddress'),
|
||||
request.headers.get('cf-connecting-ip')
|
||||
);
|
||||
return app.render(request, routeData);
|
||||
let response = await app.render(request, routeData);
|
||||
|
||||
if(app.setCookieHeaders) {
|
||||
for(const setCookieHeader of app.setCookieHeaders(response)) {
|
||||
response.headers.append('Set-Cookie', setCookieHeader);
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
return new Response(null, {
|
||||
|
|
|
@ -28,7 +28,15 @@ export function createExports(manifest: SSRManifest) {
|
|||
Symbol.for('astro.clientAddress'),
|
||||
request.headers.get('cf-connecting-ip')
|
||||
);
|
||||
return app.render(request, routeData);
|
||||
let response = await app.render(request, routeData);
|
||||
|
||||
if(app.setCookieHeaders) {
|
||||
for(const setCookieHeader of app.setCookieHeaders(response)) {
|
||||
response.headers.append('Set-Cookie', setCookieHeader);
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
return new Response(null, {
|
||||
|
|
|
@ -26,7 +26,13 @@ export function start(manifest: SSRManifest, options: Options) {
|
|||
if (app.match(request)) {
|
||||
let ip = connInfo?.remoteAddr?.hostname;
|
||||
Reflect.set(request, Symbol.for('astro.clientAddress'), ip);
|
||||
return await app.render(request);
|
||||
const response = await app.render(request);
|
||||
if(app.setCookieHeaders) {
|
||||
for(const setCookieHeader of app.setCookieHeaders(response)) {
|
||||
response.headers.append('Set-Cookie', setCookieHeader);
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
// If the request path wasn't found in astro,
|
||||
|
@ -38,7 +44,14 @@ export function start(manifest: SSRManifest, options: Options) {
|
|||
// If the static file can't be found
|
||||
if (fileResp.status == 404) {
|
||||
// Render the astro custom 404 page
|
||||
return await app.render(request);
|
||||
const response = await app.render(request);
|
||||
|
||||
if(app.setCookieHeaders) {
|
||||
for(const setCookieHeader of app.setCookieHeaders(response)) {
|
||||
response.headers.append('Set-Cookie', setCookieHeader);
|
||||
}
|
||||
}
|
||||
return response;
|
||||
|
||||
// If the static file is found
|
||||
} else {
|
||||
|
|
|
@ -17,7 +17,13 @@ export function createExports(manifest: SSRManifest) {
|
|||
if (app.match(request)) {
|
||||
const ip = request.headers.get('x-nf-client-connection-ip');
|
||||
Reflect.set(request, clientAddressSymbol, ip);
|
||||
return app.render(request);
|
||||
const response = await app.render(request);
|
||||
if(app.setCookieHeaders) {
|
||||
for(const setCookieHeader of app.setCookieHeaders(response)) {
|
||||
response.headers.append('Set-Cookie', setCookieHeader);
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
return new Response(null, {
|
||||
|
|
|
@ -120,6 +120,16 @@ export const createExports = (manifest: SSRManifest, args: Args) => {
|
|||
}
|
||||
}
|
||||
|
||||
// Apply cookies set via Astro.cookies.set/delete
|
||||
if(app.setCookieHeaders) {
|
||||
const setCookieHeaders = Array.from(app.setCookieHeaders(response));
|
||||
fnResponse.multiValueHeaders = fnResponse.multiValueHeaders || {};
|
||||
if(!fnResponse.multiValueHeaders['set-cookie']) {
|
||||
fnResponse.multiValueHeaders['set-cookie'] = [];
|
||||
}
|
||||
fnResponse.multiValueHeaders['set-cookie'].push(...setCookieHeaders);
|
||||
}
|
||||
|
||||
return fnResponse;
|
||||
};
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ export function createExports(manifest: SSRManifest) {
|
|||
if (route) {
|
||||
try {
|
||||
const response = await app.render(req);
|
||||
await writeWebResponse(res, response);
|
||||
await writeWebResponse(app, res, response);
|
||||
} catch (err: unknown) {
|
||||
if (next) {
|
||||
next(err);
|
||||
|
@ -39,8 +39,16 @@ export function createExports(manifest: SSRManifest) {
|
|||
};
|
||||
}
|
||||
|
||||
async function writeWebResponse(res: ServerResponse, webResponse: Response) {
|
||||
async function writeWebResponse(app: NodeApp, res: ServerResponse, webResponse: Response) {
|
||||
const { status, headers, body } = webResponse;
|
||||
|
||||
if(app.setCookieHeaders) {
|
||||
const setCookieHeaders: Array<string> = Array.from(app.setCookieHeaders(webResponse));
|
||||
if(setCookieHeaders.length) {
|
||||
res.setHeader('Set-Cookie', setCookieHeaders);
|
||||
}
|
||||
}
|
||||
|
||||
res.writeHead(status, Object.fromEntries(headers.entries()));
|
||||
if (body) {
|
||||
for await (const chunk of body as unknown as Readable) {
|
||||
|
|
|
@ -15,7 +15,13 @@ export function createExports(manifest: SSRManifest) {
|
|||
const handler = async (request: Request): Promise<Response> => {
|
||||
if (app.match(request)) {
|
||||
Reflect.set(request, clientAddressSymbol, request.headers.get('x-forwarded-for'));
|
||||
return await app.render(request);
|
||||
const response = await app.render(request);
|
||||
if(app.setCookieHeaders) {
|
||||
for(const setCookieHeader of app.setCookieHeaders(response)) {
|
||||
response.headers.append('Set-Cookie', setCookieHeader);
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
return new Response(null, {
|
||||
|
|
|
@ -28,7 +28,7 @@ export const createExports = (manifest: SSRManifest) => {
|
|||
return res.end('Not found');
|
||||
}
|
||||
|
||||
await setResponse(res, await app.render(request, routeData));
|
||||
await setResponse(app, res, await app.render(request, routeData));
|
||||
};
|
||||
|
||||
return { default: handler };
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import type { IncomingMessage, ServerResponse } from 'node:http';
|
||||
import type { App } from 'astro/app';
|
||||
import { Readable } from 'node:stream';
|
||||
|
||||
const clientAddressSymbol = Symbol.for('astro.clientAddress');
|
||||
|
@ -77,7 +78,7 @@ export async function getRequest(base: string, req: IncomingMessage): Promise<Re
|
|||
return request;
|
||||
}
|
||||
|
||||
export async function setResponse(res: ServerResponse, response: Response): Promise<void> {
|
||||
export async function setResponse(app: App, res: ServerResponse, response: Response): Promise<void> {
|
||||
const headers = Object.fromEntries(response.headers);
|
||||
|
||||
if (response.headers.has('set-cookie')) {
|
||||
|
@ -85,6 +86,13 @@ export async function setResponse(res: ServerResponse, response: Response): Prom
|
|||
headers['set-cookie'] = response.headers.raw()['set-cookie'];
|
||||
}
|
||||
|
||||
if(app.setCookieHeaders) {
|
||||
const setCookieHeaders: Array<string> = Array.from(app.setCookieHeaders(response));
|
||||
if(setCookieHeaders.length) {
|
||||
res.setHeader('Set-Cookie', setCookieHeaders);
|
||||
}
|
||||
}
|
||||
|
||||
res.writeHead(response.status, headers);
|
||||
|
||||
if (response.body instanceof Readable) {
|
||||
|
|
|
@ -369,6 +369,7 @@ importers:
|
|||
'@types/chai': ^4.3.1
|
||||
'@types/common-ancestor-path': ^1.0.0
|
||||
'@types/connect': ^3.4.35
|
||||
'@types/cookie': ^0.5.1
|
||||
'@types/debug': ^4.1.7
|
||||
'@types/diff': ^5.0.2
|
||||
'@types/estree': ^0.0.51
|
||||
|
@ -392,6 +393,7 @@ importers:
|
|||
cheerio: ^1.0.0-rc.11
|
||||
ci-info: ^3.3.1
|
||||
common-ancestor-path: ^1.0.1
|
||||
cookie: ^0.5.0
|
||||
debug: ^4.3.4
|
||||
diff: ^5.1.0
|
||||
eol: ^0.9.1
|
||||
|
@ -460,6 +462,7 @@ importers:
|
|||
boxen: 6.2.1
|
||||
ci-info: 3.4.0
|
||||
common-ancestor-path: 1.0.1
|
||||
cookie: 0.5.0
|
||||
debug: 4.3.4
|
||||
diff: 5.1.0
|
||||
eol: 0.9.1
|
||||
|
@ -506,6 +509,7 @@ importers:
|
|||
'@types/chai': 4.3.3
|
||||
'@types/common-ancestor-path': 1.0.0
|
||||
'@types/connect': 3.4.35
|
||||
'@types/cookie': 0.5.1
|
||||
'@types/debug': 4.1.7
|
||||
'@types/diff': 5.0.2
|
||||
'@types/estree': 0.0.51
|
||||
|
@ -1200,6 +1204,12 @@ importers:
|
|||
dependencies:
|
||||
astro: link:../../..
|
||||
|
||||
packages/astro/test/fixtures/astro-cookies:
|
||||
specifiers:
|
||||
astro: workspace:*
|
||||
dependencies:
|
||||
astro: link:../../..
|
||||
|
||||
packages/astro/test/fixtures/astro-css-bundling:
|
||||
specifiers:
|
||||
astro: workspace:*
|
||||
|
@ -4997,7 +5007,7 @@ packages:
|
|||
babel-plugin-polyfill-corejs2: 0.3.3_@babel+core@7.19.1
|
||||
babel-plugin-polyfill-corejs3: 0.6.0_@babel+core@7.19.1
|
||||
babel-plugin-polyfill-regenerator: 0.4.1_@babel+core@7.19.1
|
||||
core-js-compat: 3.25.2
|
||||
core-js-compat: 3.25.3
|
||||
semver: 6.3.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
@ -9286,6 +9296,10 @@ packages:
|
|||
'@types/node': 18.7.23
|
||||
dev: true
|
||||
|
||||
/@types/cookie/0.5.1:
|
||||
resolution: {integrity: sha512-COUnqfB2+ckwXXSFInsFdOAWQzCCx+a5hq2ruyj+Vjund94RJQd4LG2u9hnvJrTgunKAaax7ancBYlDrNYxA0g==}
|
||||
dev: true
|
||||
|
||||
/@types/debug/4.1.7:
|
||||
resolution: {integrity: sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==}
|
||||
dependencies:
|
||||
|
@ -10311,7 +10325,7 @@ packages:
|
|||
dependencies:
|
||||
'@babel/core': 7.19.1
|
||||
'@babel/helper-define-polyfill-provider': 0.3.3_@babel+core@7.19.1
|
||||
core-js-compat: 3.25.2
|
||||
core-js-compat: 3.25.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
@ -10823,8 +10837,13 @@ packages:
|
|||
engines: {node: '>= 0.6'}
|
||||
dev: true
|
||||
|
||||
/core-js-compat/3.25.2:
|
||||
resolution: {integrity: sha512-TxfyECD4smdn3/CjWxczVtJqVLEEC2up7/82t7vC0AzNogr+4nQ8vyF7abxAuTXWvjTClSbvGhU0RgqA4ToQaQ==}
|
||||
/cookie/0.5.0:
|
||||
resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/core-js-compat/3.25.3:
|
||||
resolution: {integrity: sha512-xVtYpJQ5grszDHEUU9O7XbjjcZ0ccX3LgQsyqSvTnjX97ZqEgn9F5srmrwwwMtbKzDllyFPL+O+2OFMl1lU4TQ==}
|
||||
dependencies:
|
||||
browserslist: 4.21.4
|
||||
dev: false
|
||||
|
|
Loading…
Reference in a new issue