Skip to content

Commit ee1b878

Browse files
[codex] Set Freebuff ad request user agent (#661)
Co-authored-by: James Grugett <jahooma@gmail.com>
1 parent f1b6ff3 commit ee1b878

6 files changed

Lines changed: 96 additions & 9 deletions

File tree

cli/src/hooks/use-gravity-ad.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { useChatStore } from '../state/chat-store'
77
import { isUserActive, subscribeToActivity } from '../utils/activity-tracker'
88
import { getAuthToken } from '../utils/auth'
99
import { IS_FREEBUFF } from '../utils/constants'
10+
import { getCliEnv } from '../utils/env'
1011
import { logger } from '../utils/logger'
1112

1213
import type { Message } from '@codebuff/sdk'
@@ -165,8 +166,12 @@ export const useGravityAd = (options?: {
165166
headers: {
166167
'Content-Type': 'application/json',
167168
Authorization: `Bearer ${authToken}`,
169+
'User-Agent': getCliAdRequestUserAgent(),
168170
},
169-
body: JSON.stringify({ impUrl, mode: agentMode }),
171+
body: JSON.stringify({
172+
impUrl,
173+
mode: agentMode,
174+
}),
170175
})
171176

172177
if (!res.ok) {
@@ -282,6 +287,7 @@ export const useGravityAd = (options?: {
282287
headers: {
283288
'Content-Type': 'application/json',
284289
Authorization: `Bearer ${authToken}`,
290+
'User-Agent': getCliAdRequestUserAgent(),
285291
},
286292
body: JSON.stringify({
287293
provider: providerToTry,
@@ -482,3 +488,9 @@ function getAdUserAgent(): string {
482488
}
483489
return osUA[process.platform] ?? osUA.linux
484490
}
491+
492+
function getCliAdRequestUserAgent(): string {
493+
const product = IS_FREEBUFF ? 'Freebuff-CLI' : 'Codebuff-CLI'
494+
const version = getCliEnv().CODEBUFF_CLI_VERSION ?? 'dev'
495+
return `${product}/${version}`
496+
}

web/src/app/api/v1/ads/_post.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ const bodySchema = z.object({
4646
sessionId: z.string().optional(),
4747
device: deviceSchema.optional(),
4848
surface: surfaceSchema.optional(),
49-
/** Browser/CLI useragent passed through to providers that require it. */
49+
/** Browser-like useragent passed through to providers that require it. */
5050
userAgent: z.string().optional(),
5151
})
5252

@@ -120,6 +120,7 @@ export async function postAds(params: {
120120
const providerId: AdProviderId = parsedBody.provider ?? 'gravity'
121121
const userAgent =
122122
parsedBody.userAgent ?? req.headers.get('user-agent') ?? undefined
123+
const requestUserAgent = req.headers.get('user-agent') ?? undefined
123124

124125
// Pick a provider. If the requested one isn't configured, return no ad
125126
// rather than failing — the client falls back to its cache / fallback UI.
@@ -151,6 +152,7 @@ export async function postAds(params: {
151152
sessionId: parsedBody.sessionId,
152153
clientIp,
153154
userAgent,
155+
requestUserAgent,
154156
device: parsedBody.device,
155157
surface: parsedBody.surface,
156158
messages: parsedBody.messages,

web/src/app/api/v1/ads/impression/_post.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,11 +183,16 @@ export async function postAdImpression(params: {
183183
p.replaceAll('[timestamp]', now),
184184
)
185185
const pixelUrls = [impUrl, ...extraPixels]
186+
const requestUserAgent = req.headers.get('user-agent') ?? undefined
186187

187188
await Promise.all(
188189
pixelUrls.map(async (pixelUrl) => {
189190
try {
190-
await fetch(pixelUrl)
191+
await fetch(pixelUrl, {
192+
...(requestUserAgent
193+
? { headers: { 'User-Agent': requestUserAgent } }
194+
: {}),
195+
})
191196
} catch (error) {
192197
logger.warn(
193198
{
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { describe, expect, test } from 'bun:test'
2+
3+
import { createCarbonProvider } from '../carbon'
4+
5+
import type { Logger } from '@codebuff/common/types/contracts/logger'
6+
7+
const logger: Logger = {
8+
debug: () => {},
9+
info: () => {},
10+
warn: () => {},
11+
error: () => {},
12+
}
13+
14+
describe('Carbon ad provider', () => {
15+
test('sends the CLI User-Agent as the HTTP header', async () => {
16+
const provider = createCarbonProvider({ zoneKey: 'CVADC53U' })
17+
const requests: Array<{ url: string; init?: RequestInit }> = []
18+
const fetch = Object.assign(
19+
async (url: string | URL | Request, init?: RequestInit) => {
20+
requests.push({ url: String(url), init })
21+
return new Response(
22+
JSON.stringify({
23+
ads: [
24+
{
25+
statlink: '//srv.buysellads.com/click',
26+
statimp: '//srv.buysellads.com/imp',
27+
description: 'Ad copy',
28+
company: 'Acme',
29+
},
30+
],
31+
}),
32+
{
33+
status: 200,
34+
headers: { 'Content-Type': 'application/json' },
35+
},
36+
)
37+
},
38+
{ preconnect: () => {} },
39+
) as typeof globalThis.fetch
40+
41+
const result = await provider.fetchAd({
42+
userId: 'user-1',
43+
userEmail: 'user@example.com',
44+
clientIp: '203.0.113.1',
45+
userAgent: 'Mozilla/5.0 Test Browser',
46+
requestUserAgent: 'Freebuff-CLI/0.0.88',
47+
messages: [],
48+
testMode: false,
49+
logger,
50+
fetch,
51+
})
52+
53+
expect(result?.ads).toHaveLength(1)
54+
expect(requests).toHaveLength(4)
55+
for (const request of requests) {
56+
expect(request.url).toContain('useragent=Mozilla%2F5.0+Test+Browser')
57+
expect(request.init?.headers).toEqual({
58+
'User-Agent': 'Freebuff-CLI/0.0.88',
59+
})
60+
}
61+
})
62+
})

web/src/lib/ad-providers/carbon.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,13 +95,12 @@ function normalizeCarbonAd(raw: CarbonAd): NormalizedAd | null {
9595
}
9696
}
9797

98-
export function createCarbonProvider(config: {
99-
zoneKey: string
100-
}): AdProvider {
98+
export function createCarbonProvider(config: { zoneKey: string }): AdProvider {
10199
return {
102100
id: 'carbon',
103101
fetchAd: async (input: FetchAdInput): Promise<FetchAdResult> => {
104-
const { clientIp, userAgent, testMode, logger, fetch } = input
102+
const { clientIp, userAgent, requestUserAgent, testMode, logger, fetch } =
103+
input
105104

106105
if (!clientIp || !userAgent) {
107106
logger.debug(
@@ -122,7 +121,12 @@ export function createCarbonProvider(config: {
122121
const url = `${CARBON_URL_BASE}/${config.zoneKey}.json?${params.toString()}`
123122

124123
const fetchOne = async (): Promise<NormalizedAd | null> => {
125-
const response = await fetch(url, { method: 'GET' })
124+
const response = await fetch(url, {
125+
method: 'GET',
126+
headers: {
127+
'User-Agent': requestUserAgent ?? userAgent,
128+
},
129+
})
126130
if (!response.ok) {
127131
let body: unknown
128132
try {

web/src/lib/ad-providers/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,10 @@ export type FetchAdInput = {
5353
sessionId?: string
5454
/** Client IP, parsed from X-Forwarded-For upstream. */
5555
clientIp?: string
56-
/** Browser/CLI useragent string, passed through to upstream. */
56+
/** Browser-like useragent string, passed through to upstream. */
5757
userAgent?: string
58+
/** Product User-Agent header sent on provider HTTP requests. */
59+
requestUserAgent?: string
5860
device?: AdDeviceInfo
5961
/** Product surface requesting the ad. Providers may map this to placements. */
6062
surface?: AdSurface

0 commit comments

Comments
 (0)