Detect HTTP-header cookies via CookieStore change event
Progressive enhancement for Chromium browsers: listen on the CookieStore change event to catch cookies set by Set-Cookie HTTP response headers, which the document.cookie setter hook cannot see. Adds a new "http" cookie source through the full stack. Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
@@ -20,7 +20,7 @@ import { fetchJSON } from "../http";
|
||||
interface DetectedCookieEntry {
|
||||
name: string;
|
||||
max_age_seconds: number | null;
|
||||
source: "script" | "pre-existing";
|
||||
source: "script" | "pre-existing" | "http";
|
||||
}
|
||||
|
||||
const DEBOUNCE_MS = 2_000;
|
||||
@@ -39,6 +39,7 @@ export class CookieDetector implements Detector {
|
||||
private readonly pending: Map<string, DetectedCookieEntry> = new Map();
|
||||
private timer: ReturnType<typeof setTimeout> | null = null;
|
||||
private originalDescriptor: PropertyDescriptor | null = null;
|
||||
private cookieStoreHandler: ((event: CookieChangeEvent) => void) | null = null;
|
||||
|
||||
constructor(baseUrl: URL, bannerId: string, knownNames: Set<string>) {
|
||||
this.reportUrl = new URL(`${bannerId}/report`, baseUrl);
|
||||
@@ -70,6 +71,7 @@ export class CookieDetector implements Detector {
|
||||
});
|
||||
|
||||
this.scanExisting();
|
||||
this.observeCookieStore();
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
@@ -82,6 +84,11 @@ export class CookieDetector implements Detector {
|
||||
this.flush();
|
||||
}
|
||||
|
||||
if (this.cookieStoreHandler && typeof cookieStore !== "undefined") {
|
||||
cookieStore.removeEventListener("change", this.cookieStoreHandler);
|
||||
this.cookieStoreHandler = null;
|
||||
}
|
||||
|
||||
if (this.originalDescriptor) {
|
||||
Object.defineProperty(document, "cookie", this.originalDescriptor);
|
||||
this.originalDescriptor = null;
|
||||
@@ -120,6 +127,32 @@ export class CookieDetector implements Detector {
|
||||
}
|
||||
}
|
||||
|
||||
private observeCookieStore(): void {
|
||||
if (typeof cookieStore === "undefined" || typeof cookieStore.addEventListener !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
this.cookieStoreHandler = (event: CookieChangeEvent) => {
|
||||
for (const cookie of event.changed) {
|
||||
if (this.knownNames.has(cookie.name) || this.reported.has(cookie.name)) continue;
|
||||
|
||||
const maxAge = cookie.expires
|
||||
? Math.round((cookie.expires - Date.now()) / 1000)
|
||||
: null;
|
||||
|
||||
this.reported.add(cookie.name);
|
||||
this.pending.set(cookie.name, {
|
||||
name: cookie.name,
|
||||
max_age_seconds: maxAge && maxAge > 0 ? maxAge : null,
|
||||
source: "http",
|
||||
});
|
||||
}
|
||||
if (this.pending.size > 0) this.scheduleFlush();
|
||||
};
|
||||
|
||||
cookieStore.addEventListener("change", this.cookieStoreHandler);
|
||||
}
|
||||
|
||||
private scheduleFlush(): void {
|
||||
if (this.timer) return;
|
||||
this.timer = setTimeout(() => {
|
||||
|
||||
22
packages/cookie-banner/src/globals.d.ts
vendored
22
packages/cookie-banner/src/globals.d.ts
vendored
@@ -13,3 +13,25 @@
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
declare const __SDK_VERSION__: string;
|
||||
|
||||
interface CookieListItem {
|
||||
name: string;
|
||||
value: string;
|
||||
domain: string | null;
|
||||
path: string;
|
||||
expires: number | null;
|
||||
secure: boolean;
|
||||
sameSite: "strict" | "lax" | "none";
|
||||
}
|
||||
|
||||
interface CookieChangeEvent extends Event {
|
||||
changed: readonly CookieListItem[];
|
||||
deleted: readonly CookieListItem[];
|
||||
}
|
||||
|
||||
interface CookieStoreEventTarget extends EventTarget {
|
||||
addEventListener(type: "change", listener: (event: CookieChangeEvent) => void): void;
|
||||
removeEventListener(type: "change", listener: (event: CookieChangeEvent) => void): void;
|
||||
}
|
||||
|
||||
declare const cookieStore: CookieStoreEventTarget | undefined;
|
||||
|
||||
Reference in New Issue
Block a user