Skip to content

Commit

Permalink
fix: [#1693] Adds CookieSameSiteEnum and IVirtualServer as exports to…
Browse files Browse the repository at this point in the history
… the index file (#1699)
  • Loading branch information
capricorn86 authored Jan 20, 2025
1 parent 8d1cf8f commit 02130fb
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 73 deletions.
30 changes: 19 additions & 11 deletions packages/happy-dom/src/cookie/CookieContainer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import URL from '../url/URL.js';
import DefaultCookie from './DefaultCookie.js';
import ICookie from './ICookie.js';
import ICookieContainer from './ICookieContainer.js';
import IOptionalCookie from './IOptionalCookie.js';
import CookieExpireUtility from './urilities/CookieExpireUtility.js';
import CookieURLUtility from './urilities/CookieURLUtility.js';

Expand All @@ -18,7 +20,7 @@ export default class CookieContainer implements ICookieContainer {
*
* @param cookies Cookies.
*/
public addCookies(cookies: ICookie[]): void {
public addCookies(cookies: IOptionalCookie[]): void {
const indexMap: { [k: string]: number } = {};
const getKey = (cookie: ICookie): string =>
`${cookie.key}-${cookie.originURL.hostname}-${cookie.path}-${typeof cookie.value}`;
Expand All @@ -29,18 +31,24 @@ export default class CookieContainer implements ICookieContainer {
}

for (const cookie of cookies) {
if (cookie?.key) {
// Remove existing cookie with same name, domain and path.
const index = indexMap[getKey(cookie)];
const newCookie = Object.assign({}, DefaultCookie, cookie);

if (index !== undefined) {
this.#cookies.splice(index, 1);
}
if (!newCookie || !newCookie.key || !newCookie.originURL) {
throw new Error(
"Failed to execute 'addCookies' on 'CookieContainer': The properties 'key' and 'originURL' are required."
);
}

// Remove existing cookie with same name, domain and path.
const index = indexMap[getKey(newCookie)];

if (index !== undefined) {
this.#cookies.splice(index, 1);
}

if (!CookieExpireUtility.hasExpired(cookie)) {
indexMap[getKey(cookie)] = this.#cookies.length;
this.#cookies.push(cookie);
}
if (!CookieExpireUtility.hasExpired(newCookie)) {
indexMap[getKey(newCookie)] = this.#cookies.length;
this.#cookies.push(newCookie);
}
}
}
Expand Down
17 changes: 17 additions & 0 deletions packages/happy-dom/src/cookie/DefaultCookie.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import CookieSameSiteEnum from './enums/CookieSameSiteEnum.js';
import ICookie from './ICookie.js';

export default <ICookie>{
// Required
key: null,
originURL: null,

// Optional
value: null,
domain: '',
path: '',
expires: null,
httpOnly: false,
secure: false,
sameSite: CookieSameSiteEnum.lax
};
4 changes: 1 addition & 3 deletions packages/happy-dom/src/cookie/ICookie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@ import CookieSameSiteEnum from './enums/CookieSameSiteEnum.js';
export default interface ICookie {
// Required
key: string;
value: string | null;
originURL: URL;

// Optional
value: string | null;
domain: string;
path: string;
expires: Date | null;
Expand Down
2 changes: 1 addition & 1 deletion packages/happy-dom/src/cookie/ICookieContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@ export default interface ICookieContainer {
* @param [httpOnly] "true" if only http cookies should be returned.
* @returns Cookies.
*/
getCookies(url: URL | null, httpOnly: boolean): ICookie[];
getCookies(url: URL | null, httpOnly?: boolean): ICookie[];
}
16 changes: 16 additions & 0 deletions packages/happy-dom/src/cookie/IOptionalCookie.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import CookieSameSiteEnum from './enums/CookieSameSiteEnum.js';

export default interface IOptionalCookie {
// Required
key: string;
originURL: URL;

// Optional
value?: string | null;
domain?: string;
path?: string;
expires?: Date | null;
httpOnly?: boolean;
secure?: boolean;
sameSite?: CookieSameSiteEnum;
}
15 changes: 4 additions & 11 deletions packages/happy-dom/src/cookie/urilities/CookieStringUtility.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import CookieSameSiteEnum from '../enums/CookieSameSiteEnum.js';
import URL from '../../url/URL.js';
import ICookie from '../ICookie.js';
import DefaultCookie from '../DefaultCookie.js';

/**
* Cookie string.
Expand All @@ -20,20 +21,12 @@ export default class CookieStringUtility {
const key = index !== -1 ? part.slice(0, index).trim() : part.trim();
const value = index !== -1 ? part.slice(index + 1).trim() : null;

const cookie: ICookie = {
const cookie: ICookie = Object.assign({}, DefaultCookie, {
// Required
key,
value,
originURL,

// Optional
domain: '',
path: '',
expires: null,
httpOnly: false,
secure: false,
sameSite: CookieSameSiteEnum.lax
};
originURL
});

// Invalid if key is empty.
if (!cookie.key) {
Expand Down
22 changes: 15 additions & 7 deletions packages/happy-dom/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import VirtualConsole from './console/VirtualConsole.js';
import VirtualConsolePrinter from './console/VirtualConsolePrinter.js';
import VirtualConsoleLogLevelEnum from './console/enums/VirtualConsoleLogLevelEnum.js';
import VirtualConsoleLogTypeEnum from './console/enums/VirtualConsoleLogTypeEnum.js';
import CookieSameSiteEnum from './cookie/enums/CookieSameSiteEnum.js';
import CSSRule from './css/CSSRule.js';
import CSSStyleSheet from './css/CSSStyleSheet.js';
import CSSStyleDeclaration from './css/declaration/CSSStyleDeclaration.js';
Expand All @@ -28,6 +29,8 @@ import CSSStyleRule from './css/rules/CSSStyleRule.js';
import CSSSupportsRule from './css/rules/CSSSupportsRule.js';
import CustomElementRegistry from './custom-element/CustomElementRegistry.js';
import DOMParser from './dom-parser/DOMParser.js';
import DOMRect from './dom/DOMRect.js';
import DOMRectReadOnly from './dom/DOMRectReadOnly.js';
import DataTransfer from './event/DataTransfer.js';
import DataTransferItem from './event/DataTransferItem.js';
import DataTransferItemList from './event/DataTransferItemList.js';
Expand Down Expand Up @@ -74,8 +77,6 @@ import Comment from './nodes/comment/Comment.js';
import DocumentFragment from './nodes/document-fragment/DocumentFragment.js';
import DocumentType from './nodes/document-type/DocumentType.js';
import Document from './nodes/document/Document.js';
import DOMRect from './dom/DOMRect.js';
import DOMRectReadOnly from './dom/DOMRectReadOnly.js';
import Element from './nodes/element/Element.js';
import HTMLCollection from './nodes/element/HTMLCollection.js';
import HTMLAnchorElement from './nodes/html-anchor-element/HTMLAnchorElement.js';
Expand Down Expand Up @@ -188,10 +189,12 @@ import type IBrowserFrame from './browser/types/IBrowserFrame.js';
import type IBrowserPage from './browser/types/IBrowserPage.js';
import type IBrowserSettings from './browser/types/IBrowserSettings.js';
import type IOptionalBrowserSettings from './browser/types/IOptionalBrowserSettings.js';
import type ICookie from './cookie/ICookie.js';
import type IOptionalCookie from './cookie/IOptionalCookie.js';
import type IEventInit from './event/IEventInit.js';
import type TEventListener from './event/TEventListener.js';
import type ITouchInit from './event/ITouchInit.js';
import type IUIEventInit from './event/IUIEventInit.js';
import type TEventListener from './event/TEventListener.js';
import type IAnimationEventInit from './event/events/IAnimationEventInit.js';
import type IClipboardEventInit from './event/events/IClipboardEventInit.js';
import type ICustomEventInit from './event/events/ICustomEventInit.js';
Expand All @@ -206,34 +209,38 @@ import type IProgressEventInit from './event/events/IProgressEventInit.js';
import type ISubmitEventInit from './event/events/ISubmitEventInit.js';
import type ITouchEventInit from './event/events/ITouchEventInit.js';
import type IWheelEventInit from './event/events/IWheelEventInit.js';
import type IVirtualServer from './fetch/types/IVirtualServer.js';

export type {
IFetchInterceptor,
ISyncResponse,
IAnimationEventInit,
IBrowser,
IBrowserContext,
IBrowserFrame,
IBrowserPage,
IBrowserSettings,
IClipboardEventInit,
ICookie,
ICustomEventInit,
IErrorEventInit,
IEventInit,
TEventListener,
IFetchInterceptor,
IFocusEventInit,
IHashChangeEventInit,
IInputEventInit,
IKeyboardEventInit,
IMediaQueryListInit,
IMouseEventInit,
IOptionalBrowserSettings,
IOptionalCookie,
IProgressEventInit,
ISubmitEventInit,
ISyncResponse,
ITouchEventInit,
ITouchInit,
IUIEventInit,
IWheelEventInit
IVirtualServer,
IWheelEventInit,
TEventListener
};

export {
Expand All @@ -253,6 +260,7 @@ export {
ClipboardEvent,
ClipboardItem,
Comment,
CookieSameSiteEnum,
CSSContainerRule,
CSSFontFaceRule,
CSSKeyframeRule,
Expand Down
118 changes: 78 additions & 40 deletions packages/happy-dom/test/cookie/CookieContainer.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import CookieContainer from '../../src/cookie/CookieContainer.js';
import CookieSameSiteEnum from '../../src/cookie/enums/CookieSameSiteEnum.js';
import ICookie from '../../src/cookie/ICookie.js';
import ICookieContainer from '../../src/cookie/ICookieContainer.js';
import CookieStringUtility from '../../src/cookie/urilities/CookieStringUtility.js';
Expand Down Expand Up @@ -151,13 +152,7 @@ describe('CookieContainer', () => {
const originURL = new URL('https://example.com/path/to/page/');
const targetURL = new URL('https://example.com/path/to/page/');

cookieContainer.addCookies([
<ICookie>CookieStringUtility.stringToCookie(originURL, `__secure-key=value`)
]);

expect(
CookieStringUtility.cookiesToString(cookieContainer.getCookies(targetURL, false))
).toBe('');
expect(CookieStringUtility.stringToCookie(originURL, `__secure-key=value`)).toBe(null);

cookieContainer.addCookies([
<ICookie>CookieStringUtility.stringToCookie(originURL, `__secure-key=value; Secure;`)
Expand All @@ -172,55 +167,98 @@ describe('CookieContainer', () => {
const originURL = new URL('https://example.com/path/to/page/');
const targetURL = new URL('https://example.com/path/to/page/');

cookieContainer.addCookies([
<ICookie>CookieStringUtility.stringToCookie(originURL, `__host-key=value`)
]);

expect(
CookieStringUtility.cookiesToString(cookieContainer.getCookies(targetURL, false))
).toBe('');
expect(CookieStringUtility.stringToCookie(originURL, `__host-key=value`)).toBe(null);

cookieContainer.addCookies([
<ICookie>CookieStringUtility.stringToCookie(originURL, `__host-key=value; Secure;`)
]);
expect(CookieStringUtility.stringToCookie(originURL, `__host-key=value; Secure;`)).toBe(null);

expect(
CookieStringUtility.cookiesToString(cookieContainer.getCookies(targetURL, false))
).toBe('');

cookieContainer.addCookies([
<ICookie>(
CookieStringUtility.stringToCookie(
originURL,
`__host-key=value; Secure; Path=/path/to/page/;`
)
CookieStringUtility.stringToCookie(
originURL,
`__host-key=value; Secure; Path=/path/to/page/;`
)
]);
).toBe(null);

expect(
CookieStringUtility.cookiesToString(cookieContainer.getCookies(targetURL, false))
).toBe('');
CookieStringUtility.stringToCookie(
originURL,
`__host-key=value; Secure; Domain=example.com; Path=/;`
)
).toBe(null);

cookieContainer.addCookies([
<ICookie>(
CookieStringUtility.stringToCookie(
originURL,
`__host-key=value; Secure; Domain=example.com; Path=/;`
)
)
<ICookie>CookieStringUtility.stringToCookie(originURL, `__host-key=value; Secure; Path=/;`)
]);

expect(
CookieStringUtility.cookiesToString(cookieContainer.getCookies(targetURL, false))
).toBe('');
).toBe('__host-key=value');
});

it('Throws an error if the cookie "key" is missing.', () => {
const originURL = new URL('https://example.com/path/to/page/');
expect(() => {
cookieContainer.addCookies([<ICookie>{ originURL }]);
}).toThrowError(
"Failed to execute 'addCookies' on 'CookieContainer': The properties 'key' and 'originURL' are required."
);
});

it('Throws an error if the cookie "originURL" is missing.', () => {
expect(() => {
cookieContainer.addCookies([<ICookie>{ key: 'key' }]);
}).toThrowError(
"Failed to execute 'addCookies' on 'CookieContainer': The properties 'key' and 'originURL' are required."
);
});
});

describe('getCookies()', () => {
it('Returns cookies.', () => {
const originURL = new URL('https://example.com/path/to/page/');
const expires = new Date(60 * 1000 + Date.now());

cookieContainer.addCookies([
<ICookie>CookieStringUtility.stringToCookie(originURL, `__host-key=value; Secure; Path=/;`)
{
key: 'key1',
originURL
},
{
key: 'key2',
originURL,
value: 'value2',
domain: 'example.com',
path: '/path/to/page/',
expires,
httpOnly: true,
secure: true,
sameSite: CookieSameSiteEnum.strict
}
]);

expect(
CookieStringUtility.cookiesToString(cookieContainer.getCookies(targetURL, false))
).toBe('__host-key=value');
expect(cookieContainer.getCookies(originURL)).toEqual([
{
key: 'key1',
originURL,
value: null,
domain: '',
path: '',
expires: null,
httpOnly: false,
secure: false,
sameSite: CookieSameSiteEnum.lax
},
{
key: 'key2',
originURL,
value: 'value2',
domain: 'example.com',
path: '/path/to/page/',
expires,
httpOnly: true,
secure: true,
sameSite: CookieSameSiteEnum.strict
}
]);
});
});
});

0 comments on commit 02130fb

Please sign in to comment.