diff --git a/README.md b/README.md index 97ccea4..9b10b5c 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,18 @@ Bindings should be set in your `wrangler.jsonc`, or a `.dev.vars.local` file whe `SURFACE_DECISIONS_IGNORE_PATHS` is a comma-separated list of regular expressions. Matching pathnames still proxy to the origin and rewrite origin links, but skip MonetizationOS surface decisions and component transforms. +## Optional: cookies forwarded to surface decisions + +`SURFACE_DECISIONS_COOKIES` is a comma-separated list of regular expressions. Cookie names matching any pattern are forwarded to the MonetizationOS surface-decisions API as `http.cookies`. Matching cookies are read from the incoming request `Cookie` header and from the origin response `Set-Cookie` headers; when the same name appears in both, the origin value is used. When unset or when no cookies match, `http.cookies` is omitted from the surface-decisions payload. + +In `wrangler.jsonc` `vars`: + +```jsonc +"SURFACE_DECISIONS_COOKIES": "^__session$, ^theme$, ^mos_" +``` + +Each pattern is a regex tested against the cookie **name**. Plain names like `^__session$` match exactly; prefixes like `^mos_` match any cookie whose name starts with `mos_`. + ## Optional: headers sent to the origin `ORIGIN_REQUEST_HEADERS` adds or overrides outgoing headers on every upstream fetch to `ORIGIN_URL`. Set it to a JSON object mapping header names to values. Leave it as `{}` when no extra headers are needed. diff --git a/package-lock.json b/package-lock.json index 5b677b5..db93a3d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.1", "license": "MIT", "dependencies": { - "@monetizationos/proxy": "1.4.2" + "@monetizationos/proxy": "1.4.3" }, "devDependencies": { "@biomejs/biome": "2.4.16", @@ -376,7 +376,6 @@ "dev": true, "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "bin": { "workerd": "bin/workerd" }, @@ -1518,9 +1517,9 @@ } }, "node_modules/@monetizationos/proxy": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@monetizationos/proxy/-/proxy-1.4.2.tgz", - "integrity": "sha512-OqlAFVVw/vQcS4q96t4ZUEEFZNVIfxRl8FM4ZS+X+TuWnVA8JLJDpE8LicVwhoWJjoYcuYucz4nJhSMJRFJFVw==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@monetizationos/proxy/-/proxy-1.4.3.tgz", + "integrity": "sha512-nSzTbQVLzXripFF8bzXRhZ2nqZVIQLwZINspE/HARBeBv2fzm0YQhqvEXnqmgoMHAZjxOPhkw8E3ELRKnlP2dg==", "license": "MIT", "dependencies": { "cookie": "^1.1.1" @@ -1959,7 +1958,6 @@ "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } @@ -2027,7 +2025,6 @@ "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/utils": "3.2.4", "pathe": "^2.0.3", @@ -2043,7 +2040,6 @@ "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/pretty-format": "3.2.4", "magic-string": "^0.30.17", @@ -2423,7 +2419,6 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -2793,7 +2788,6 @@ "integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "pathe": "^2.0.3" } @@ -2804,7 +2798,6 @@ "integrity": "sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -2903,7 +2896,6 @@ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", @@ -2995,7 +2987,6 @@ "dev": true, "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "bin": { "workerd": "bin/workerd" }, diff --git a/package.json b/package.json index 4a7c68b..bf34776 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,9 @@ "SURFACE_DECISIONS_IGNORE_PATHS": { "description": "Comma-separated regex patterns for paths that should skip surface decisions." }, + "SURFACE_DECISIONS_COOKIES": { + "description": "Comma-separated regex patterns for cookie names forwarded to surface decisions as http.cookies." + }, "ORIGIN_REQUEST_HEADERS": { "description": "Optional JSON object mapping header names to values added to every origin request (e.g. {\"X-Api-Key\":\"secret\"}). Use {} when unset." } @@ -53,6 +56,6 @@ "wrangler": "^4.95.0" }, "dependencies": { - "@monetizationos/proxy": "1.4.2" + "@monetizationos/proxy": "1.4.3" } } diff --git a/src/index.ts b/src/index.ts index efff5d3..92b4aaf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,6 +12,7 @@ const proxy = new MOSProxyBuilder() authenticatedUserJwtCookieName: env.AUTHENTICATED_USER_JWT_COOKIE_NAME, injectScriptUrl: env.INJECT_SCRIPT_URL || undefined, surfaceDecisionsIgnorePaths: env.SURFACE_DECISIONS_IGNORE_PATHS, + surfaceDecisionsCookies: env.SURFACE_DECISIONS_COOKIES, originRequestHeaders: env.ORIGIN_REQUEST_HEADERS ?? {}, }) .withOriginFetcher(fetch) diff --git a/test/proxyConfig.spec.ts b/test/proxyConfig.spec.ts index 54e09c6..74f72b6 100644 --- a/test/proxyConfig.spec.ts +++ b/test/proxyConfig.spec.ts @@ -1,7 +1,7 @@ import { fetchMock } from 'cloudflare:test' import { env } from 'cloudflare:workers' import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest' -import { mockSurfaceDecisionsFetch } from './helpers' +import { mockOriginFetch, mockSurfaceDecisionsFetch } from './helpers' describe('proxy config', () => { beforeAll(() => { @@ -77,6 +77,32 @@ describe('proxy config', () => { expect(await res.json()).toEqual({ ok: true }) }) + it('forwards matching cookies to surface decisions when SURFACE_DECISIONS_COOKIES is set', async () => { + mockOriginFetch({ + responseHeaders: { 'Set-Cookie': 'theme=from-origin; Path=/' }, + }) + const mockSurfaceDecision = mockSurfaceDecisionsFetch() + + const res = await fetchWithFreshWorker( + new Request('https://test.example/index.html', { + headers: { Cookie: 'jwt-cookie=request-jwt; theme=old; ignored=1' }, + }), + { SURFACE_DECISIONS_COOKIES: '^jwt-cookie$, ^theme$' }, + ) + + expect(res.status).toBe(200) + expect(mockSurfaceDecision).toHaveBeenCalledExactlyOnceWith( + expect.objectContaining({ + http: expect.objectContaining({ + cookies: { + 'jwt-cookie': 'request-jwt', + theme: 'from-origin', + }, + }), + }), + ) + }) + it('merges configured origin request headers and preserves untouched client headers', async () => { fetchMock .get('https://origin.example') diff --git a/worker-configuration.d.ts b/worker-configuration.d.ts index a01398e..f95bc95 100644 --- a/worker-configuration.d.ts +++ b/worker-configuration.d.ts @@ -15,6 +15,7 @@ declare namespace Cloudflare { MONETIZATION_OS_ENDPOINTS_PREFIX: string; MONETIZATION_OS_SECRET_KEY: string; SURFACE_DECISIONS_IGNORE_PATHS: string; + SURFACE_DECISIONS_COOKIES: string; ORIGIN_REQUEST_HEADERS: Record; } }