(ad: AdResponse)
| 141 | |
| 142 | // Fire impression and update credits (called when showing an ad) |
| 143 | const recordImpressionOnce = (ad: AdResponse): void => { |
| 144 | // Don't record impressions when ads should be hidden |
| 145 | if (shouldHideAdsRef.current) return |
| 146 | |
| 147 | const ctrl = ctrlRef.current |
| 148 | const { impUrl } = ad |
| 149 | if (ctrl.impressionsFired.has(impUrl)) return |
| 150 | ctrl.impressionsFired.add(impUrl) |
| 151 | |
| 152 | const recordLocalImpression = async (): Promise<void> => { |
| 153 | const authToken = getAuthToken() |
| 154 | if (!authToken) { |
| 155 | logger.warn('[ads] No auth token, skipping local impression recording') |
| 156 | return |
| 157 | } |
| 158 | |
| 159 | // Include mode in request - Freebuff should not grant credits (no balance concept). |
| 160 | const agentMode = useChatStore.getState().agentMode |
| 161 | |
| 162 | const res = await fetch(`${WEBSITE_URL}/api/v1/ads/impression`, { |
| 163 | method: 'POST', |
| 164 | headers: { |
| 165 | 'Content-Type': 'application/json', |
| 166 | Authorization: `Bearer ${authToken}`, |
| 167 | 'User-Agent': getCliAdRequestUserAgent(), |
| 168 | }, |
| 169 | body: JSON.stringify({ |
| 170 | impUrl, |
| 171 | mode: agentMode, |
| 172 | }), |
| 173 | }) |
| 174 | |
| 175 | if (!res.ok) { |
| 176 | logger.debug( |
| 177 | { status: res.status }, |
| 178 | '[ads] Failed to record local ad impression', |
| 179 | ) |
| 180 | return |
| 181 | } |
| 182 | |
| 183 | const data = await res.json() |
| 184 | if (data.creditsGranted > 0) { |
| 185 | logger.info( |
| 186 | { creditsGranted: data.creditsGranted }, |
| 187 | '[ads] Ad impression credits granted', |
| 188 | ) |
| 189 | // Also update credits in visible ads |
| 190 | setAds((cur) => { |
| 191 | if (!cur) return cur |
| 192 | return cur.map((a) => |
| 193 | a.impUrl === impUrl ? { ...a, credits: data.creditsGranted } : a, |
| 194 | ) |
| 195 | }) |
| 196 | } |
| 197 | } |
| 198 | |
| 199 | if (ad.provider === 'zeroclick' && ad.impressionIds?.length) { |
| 200 | void (async () => { |
nothing calls this directly
no test coverage detected