Skip to content

Commit 48c4854

Browse files
Sync public snapshot from freebuff-private
Source: CodebuffAI/freebuff-private@345eb370dcc2fd3e1cbe90eb8670296e9f5afab6
1 parent f39bae7 commit 48c4854

3 files changed

Lines changed: 162 additions & 0 deletions

File tree

‎bun.lock‎

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { describe, expect, test } from 'bun:test'
2+
import {
3+
GOOGLE_ADS_CLICK_WINDOW_MS,
4+
normalizeGoogleAdsAttribution,
5+
parseGoogleAdsClickCookie,
6+
} from '../util/google-ads-conversions'
7+
8+
const now = Date.parse('2026-10-21T12:00:00Z')
9+
describe('Google Ads attribution contract', () => {
10+
test('retains one identifier by priority and discards unrelated fields', () => {
11+
expect(
12+
normalizeGoogleAdsAttribution(
13+
{
14+
gclid: 'click_1-~.test',
15+
gbraid: 'braid',
16+
wbraid: 'web',
17+
capturedAt: now,
18+
email: 'private@example.com',
19+
url: '/private',
20+
},
21+
now,
22+
),
23+
).toEqual({ gclid: 'click_1-~.test', capturedAt: now })
24+
expect(
25+
normalizeGoogleAdsAttribution(
26+
{ gbraid: 'braid', wbraid: 'web', capturedAt: now },
27+
now,
28+
),
29+
).toEqual({ gbraid: 'braid', capturedAt: now })
30+
expect(
31+
normalizeGoogleAdsAttribution({ wbraid: 'web', capturedAt: now }, now),
32+
).toEqual({ wbraid: 'web', capturedAt: now })
33+
})
34+
test.each([
35+
'',
36+
'a'.repeat(513),
37+
'a/b',
38+
'a+b',
39+
'a b',
40+
'<script>',
41+
'a=1',
42+
12,
43+
null,
44+
])('rejects unsafe identifiers %p', (gclid) => {
45+
expect(
46+
normalizeGoogleAdsAttribution({ gclid, capturedAt: now }, now),
47+
).toBeUndefined()
48+
})
49+
test.each([
50+
now + 1,
51+
now - GOOGLE_ADS_CLICK_WINDOW_MS,
52+
NaN,
53+
Infinity,
54+
String(now),
55+
undefined,
56+
])('rejects invalid/expired timestamp %p', (capturedAt) => {
57+
expect(
58+
normalizeGoogleAdsAttribution({ gclid: 'click', capturedAt }, now),
59+
).toBeUndefined()
60+
})
61+
test('accepts the age boundary only while still inside ninety days', () => {
62+
const attribution = {
63+
gclid: 'a'.repeat(512),
64+
capturedAt: now - GOOGLE_ADS_CLICK_WINDOW_MS + 1,
65+
}
66+
expect(normalizeGoogleAdsAttribution(attribution, now)).toEqual(attribution)
67+
expect(
68+
parseGoogleAdsClickCookie(
69+
encodeURIComponent(JSON.stringify(attribution)),
70+
now,
71+
),
72+
).toEqual(attribution)
73+
expect(parseGoogleAdsClickCookie('%malformed', now)).toBeUndefined()
74+
})
75+
})
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { META_ADS_PERMISSION_COOKIE } from './meta-ads'
2+
import { META_TRACKING_PERMISSION_COOKIE } from './meta-conversions'
3+
import { PAID_SOCIAL_PERMISSION_COOKIE } from './paid-social-conversions'
4+
import { X_ADVERTISER_PERMISSION_COOKIE } from './x-advertiser'
5+
6+
/** First-party eligibility marker, not a record of explicit consent. */
7+
export const GOOGLE_ADS_PERMISSION_COOKIE = 'freebuff_google_ads_allowed'
8+
export const GOOGLE_ADS_CLICK_COOKIE = 'freebuff_google_ads_click'
9+
export const GOOGLE_ADS_CLICK_WINDOW_MS = 90 * 24 * 60 * 60 * 1000
10+
export const GOOGLE_ADS_CLICK_KEYS = ['gclid', 'gbraid', 'wbraid'] as const
11+
12+
export type GoogleAdsAttribution = {
13+
gclid?: string
14+
gbraid?: string
15+
wbraid?: string
16+
capturedAt: number
17+
}
18+
19+
export function validGoogleAdsClickId(value: unknown): string | undefined {
20+
return typeof value === 'string' && /^[A-Za-z0-9._~-]{1,512}$/.test(value)
21+
? value
22+
: undefined
23+
}
24+
25+
/** One identifier only, in Google's priority order; no URL or user data. */
26+
export function normalizeGoogleAdsAttribution(
27+
value: unknown,
28+
now = Date.now(),
29+
): GoogleAdsAttribution | undefined {
30+
if (!value || typeof value !== 'object' || Array.isArray(value)) return
31+
const input = value as Record<string, unknown>
32+
const capturedAt = input.capturedAt
33+
if (
34+
typeof capturedAt !== 'number' ||
35+
!Number.isFinite(capturedAt) ||
36+
!Number.isFinite(now) ||
37+
capturedAt > now ||
38+
now - capturedAt >= GOOGLE_ADS_CLICK_WINDOW_MS
39+
)
40+
return
41+
for (const key of GOOGLE_ADS_CLICK_KEYS) {
42+
if (input[key] !== undefined && !validGoogleAdsClickId(input[key])) return
43+
}
44+
for (const key of GOOGLE_ADS_CLICK_KEYS) {
45+
const id = validGoogleAdsClickId(input[key])
46+
if (id) return { [key]: id, capturedAt }
47+
}
48+
return undefined
49+
}
50+
51+
export function parseGoogleAdsClickCookie(
52+
value: string | undefined,
53+
now = Date.now(),
54+
): GoogleAdsAttribution | undefined {
55+
if (!value || value.length > 4096) return
56+
try {
57+
return normalizeGoogleAdsAttribution(
58+
JSON.parse(decodeURIComponent(value)),
59+
now,
60+
)
61+
} catch {
62+
return undefined
63+
}
64+
}
65+
66+
const DECLINABLE_PERMISSION_COOKIES = [
67+
GOOGLE_ADS_PERMISSION_COOKIE,
68+
META_TRACKING_PERMISSION_COOKIE,
69+
META_ADS_PERMISSION_COOKIE,
70+
PAID_SOCIAL_PERMISSION_COOKIE,
71+
X_ADVERTISER_PERMISSION_COOKIE,
72+
]
73+
74+
export function googleAdsPermissionDeclined(
75+
readCookie: (name: string) => string | undefined,
76+
): boolean {
77+
return DECLINABLE_PERMISSION_COOKIES.some((name) => readCookie(name) === '0')
78+
}
79+
80+
export function googleAdsTrackingOptedOut(headers: {
81+
get(name: string): string | null
82+
}): boolean {
83+
const dnt = headers.get('dnt')?.toLowerCase()
84+
return headers.get('sec-gpc') === '1' || dnt === '1' || dnt === 'yes'
85+
}

0 commit comments

Comments
 (0)