mirror of
https://github.com/withastro/astro.git
synced 2025-01-06 22:10:10 -05:00
Allow setting all cookie package serialize/parse options (#9063)
* #9062: allow setting all cookie package serialize/parse options * 9062: fix scripts to original arrangement * feat: only add specific properties * Update tiny-days-dance.md * Add examples to the changeset * Update .changeset/tiny-days-dance.md Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Update .changeset/tiny-days-dance.md Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Update .changeset/tiny-days-dance.md Co-authored-by: Emanuele Stoppa <my.burning@gmail.com> * Update .changeset/tiny-days-dance.md Co-authored-by: Emanuele Stoppa <my.burning@gmail.com> --------- Co-authored-by: Arsh <69170106+lilnasy@users.noreply.github.com> Co-authored-by: Florian Lefebvre <contact@florian-lefebvre.dev> Co-authored-by: Matthew Phillips <matthew@skypack.dev> Co-authored-by: Matthew Phillips <matthew@matthewphillips.info> Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>
This commit is contained in:
parent
08402ad584
commit
f33fe3190b
6 changed files with 91 additions and 10 deletions
28
.changeset/tiny-days-dance.md
Normal file
28
.changeset/tiny-days-dance.md
Normal file
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
'astro': minor
|
||||
---
|
||||
|
||||
Cookie encoding / decoding can now be customized
|
||||
|
||||
Adds new `encode` and `decode` functions to allow customizing how cookies are encoded and decoded. For example, you can bypass the default encoding via `encodeURIComponent` when adding a URL as part of a cookie:
|
||||
|
||||
```astro
|
||||
---
|
||||
import { encodeCookieValue } from "./cookies";
|
||||
Astro.cookies.set('url', Astro.url.toString(), {
|
||||
// Override the default encoding so that URI components are not encoded
|
||||
encode: value => encodeCookieValue(value)
|
||||
});
|
||||
---
|
||||
```
|
||||
|
||||
Later, you can decode the URL in the same way:
|
||||
|
||||
```astro
|
||||
---
|
||||
import { decodeCookieValue } from "./cookies";
|
||||
const url = Astro.cookies.get('url', {
|
||||
decode: value => decodeCookieValue(value)
|
||||
});
|
||||
---
|
||||
```
|
|
@ -64,7 +64,7 @@ export type {
|
|||
} from '../assets/types.js';
|
||||
export type { RemotePattern } from '../assets/utils/remotePattern.js';
|
||||
export type { SSRManifest } from '../core/app/types.js';
|
||||
export type { AstroCookies } from '../core/cookies/index.js';
|
||||
export type { AstroCookies, AstroCookieSetOptions, AstroCookieGetOptions } from '../core/cookies/index.js';
|
||||
|
||||
export interface AstroBuiltinProps {
|
||||
'client:load'?: boolean;
|
||||
|
|
|
@ -2,7 +2,7 @@ import type { CookieSerializeOptions } from 'cookie';
|
|||
import { parse, serialize } from 'cookie';
|
||||
import { AstroError, AstroErrorData } from '../errors/index.js';
|
||||
|
||||
interface AstroCookieSetOptions {
|
||||
export interface AstroCookieSetOptions {
|
||||
domain?: string;
|
||||
expires?: Date;
|
||||
httpOnly?: boolean;
|
||||
|
@ -10,8 +10,13 @@ interface AstroCookieSetOptions {
|
|||
path?: string;
|
||||
sameSite?: boolean | 'lax' | 'none' | 'strict';
|
||||
secure?: boolean;
|
||||
encode?: (value: string) => string;
|
||||
}
|
||||
|
||||
export interface AstroCookieGetOptions {
|
||||
decode?: (value: string) => string;
|
||||
};
|
||||
|
||||
type AstroCookieDeleteOptions = Pick<AstroCookieSetOptions, 'domain' | 'path'>;
|
||||
|
||||
interface AstroCookieInterface {
|
||||
|
@ -97,7 +102,7 @@ class AstroCookies implements AstroCookiesInterface {
|
|||
* @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 | undefined {
|
||||
get(key: string, options: AstroCookieGetOptions | undefined = undefined): AstroCookie | undefined {
|
||||
// Check for outgoing Set-Cookie values first
|
||||
if (this.#outgoing?.has(key)) {
|
||||
let [serializedValue, , isSetValue] = this.#outgoing.get(key)!;
|
||||
|
@ -108,7 +113,7 @@ class AstroCookies implements AstroCookiesInterface {
|
|||
}
|
||||
}
|
||||
|
||||
const values = this.#ensureParsed();
|
||||
const values = this.#ensureParsed(options);
|
||||
if (key in values) {
|
||||
const value = values[key];
|
||||
return new AstroCookie(value);
|
||||
|
@ -121,12 +126,12 @@ class AstroCookies implements AstroCookiesInterface {
|
|||
* @param key The cookie to check for.
|
||||
* @returns
|
||||
*/
|
||||
has(key: string): boolean {
|
||||
has(key: string, options: AstroCookieGetOptions | undefined = undefined): boolean {
|
||||
if (this.#outgoing?.has(key)) {
|
||||
let [, , isSetValue] = this.#outgoing.get(key)!;
|
||||
return isSetValue;
|
||||
}
|
||||
const values = this.#ensureParsed();
|
||||
const values = this.#ensureParsed(options);
|
||||
return !!values[key];
|
||||
}
|
||||
|
||||
|
@ -185,9 +190,9 @@ class AstroCookies implements AstroCookiesInterface {
|
|||
}
|
||||
}
|
||||
|
||||
#ensureParsed(): Record<string, string> {
|
||||
#ensureParsed(options: AstroCookieGetOptions | undefined = undefined): Record<string, string> {
|
||||
if (!this.#requestValues) {
|
||||
this.#parse();
|
||||
this.#parse(options);
|
||||
}
|
||||
if (!this.#requestValues) {
|
||||
this.#requestValues = {};
|
||||
|
@ -202,13 +207,13 @@ class AstroCookies implements AstroCookiesInterface {
|
|||
return this.#outgoing;
|
||||
}
|
||||
|
||||
#parse() {
|
||||
#parse(options: AstroCookieGetOptions | undefined = undefined) {
|
||||
const raw = this.#request.headers.get('cookie');
|
||||
if (!raw) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#requestValues = parse(raw);
|
||||
this.#requestValues = parse(raw, options);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,3 +4,4 @@ export {
|
|||
getSetCookiesFromResponse,
|
||||
responseHasCookies,
|
||||
} from './response.js';
|
||||
export type { AstroCookieSetOptions, AstroCookieGetOptions } from "./cookies.js";
|
|
@ -16,6 +16,30 @@ describe('astro/src/core/cookies', () => {
|
|||
expect(cookies.get('foo').value).to.equal('bar');
|
||||
});
|
||||
|
||||
it('gets the cookie value with default decode', () => {
|
||||
const url = 'http://localhost';
|
||||
const req = new Request('http://example.com/', {
|
||||
headers: {
|
||||
cookie: `url=${encodeURIComponent(url)}`,
|
||||
},
|
||||
});
|
||||
const cookies = new AstroCookies(req);
|
||||
// by default decodeURIComponent is used on the value
|
||||
expect(cookies.get('url').value).to.equal(url);
|
||||
});
|
||||
|
||||
it('gets the cookie value with custom decode', () => {
|
||||
const url = 'http://localhost';
|
||||
const req = new Request('http://example.com/', {
|
||||
headers: {
|
||||
cookie: `url=${encodeURIComponent(url)}`,
|
||||
},
|
||||
});
|
||||
const cookies = new AstroCookies(req);
|
||||
// set decode to the identity function to prevent decodeURIComponent on the value
|
||||
expect(cookies.get('url', { decode: o => o }).value).to.equal(encodeURIComponent(url));
|
||||
});
|
||||
|
||||
it("Returns undefined is the value doesn't exist", () => {
|
||||
const req = new Request('http://example.com/');
|
||||
let cookies = new AstroCookies(req);
|
||||
|
|
|
@ -15,6 +15,29 @@ describe('astro/src/core/cookies', () => {
|
|||
expect(headers[0]).to.equal('foo=bar');
|
||||
});
|
||||
|
||||
it('Sets a cookie value that can be serialized w/ defaultencodeURIComponent behavior', () => {
|
||||
let req = new Request('http://example.com/');
|
||||
let cookies = new AstroCookies(req);
|
||||
const url = 'http://localhost/path';
|
||||
cookies.set('url', url);
|
||||
let headers = Array.from(cookies.headers());
|
||||
expect(headers).to.have.a.lengthOf(1);
|
||||
// by default cookie value is URI encoded
|
||||
expect(headers[0]).to.equal(`url=${encodeURIComponent(url)}`);
|
||||
});
|
||||
|
||||
it('Sets a cookie value that can be serialized w/ custom encode behavior', () => {
|
||||
let req = new Request('http://example.com/');
|
||||
let cookies = new AstroCookies(req);
|
||||
const url = 'http://localhost/path';
|
||||
// set encode option to the identity function
|
||||
cookies.set('url', url, { encode: o => o });
|
||||
let headers = Array.from(cookies.headers());
|
||||
expect(headers).to.have.a.lengthOf(1);
|
||||
// no longer URI encoded
|
||||
expect(headers[0]).to.equal(`url=${url}`);
|
||||
});
|
||||
|
||||
it('Can set cookie options', () => {
|
||||
let req = new Request('http://example.com/');
|
||||
let cookies = new AstroCookies(req);
|
||||
|
|
Loading…
Reference in a new issue