@adsoverai/react
v1.2.2
Published
React SDK for AdsOverAI - Native advertising for AI chat interfaces
Maintainers
Readme
@adsoverai/react
Native Advertising SDK for AI Chat Interfaces
Quick Start • Documentation • Examples • API Reference • Support
Table of Contents
- Overview
- Features
- Installation
- Quick Start
- Architecture
- Core Concepts
- Configuration
- API Reference
- Advanced Usage
- Impression Tracking Deep Dive
- Examples
- Troubleshooting
- Performance
- Browser Support
- Contributing
- Support
- License
Overview
@adsoverai/react is a production-ready SDK that brings native advertising to AI-powered chat interfaces. Built with React and TypeScript, it provides context-aware ads with robust viewability tracking that ensures advertisers only pay for ads that users actually see.
Why AdsOverAI?
- 🎯 Smart Ad Matching - Leverages AI to display relevant ads based on conversation context
- 👁️ IAB-Compliant Tracking - Industry-standard viewability measurement
- 🔒 Built-in Fraud Prevention - Cryptographic tokens and runtime integrity checks
- ⚡ Developer Friendly - Simple API, full TypeScript support, comprehensive documentation
- 🎨 Customizable - Flexible theming and custom rendering options
- 📦 Lightweight - Only ~15KB gzipped with intelligent caching
Features
🎯 Context-Aware Ads
Display relevant ads based on AI conversation context. Our backend intelligently matches ads to user queries and AI responses for maximum relevance.
👁️ Verified Impression Tracking
IAB-compliant viewability tracking with fraud prevention. Unlike traditional ad SDKs, we ensure advertisers only pay for ads that users actually see:
- ✅ IntersectionObserver-Based - Precise viewport detection with configurable thresholds
- ✅ Time-Validated - Ads must remain visible for a minimum duration (default: 1 second)
- ✅ Fraud Prevention - Cryptographic impression tokens prevent replay attacks
- ✅ DOM Validation - Detects hidden ads (opacity: 0, display: none, etc.)
- ✅ Runtime Integrity Checks - Browser environment validation with trust scoring
- ✅ Deduplication - Each impression token can only be redeemed once
🔒 Security-First Architecture
- Cryptographic tokens for impression verification
- Runtime integrity checks detect tampered browser APIs
- HTTPS-only secure context enforcement
- Trust scoring for each impression (0.0-1.0)
- Backend rate limiting and behavioral analysis
⚡ Performance Optimized
- 5-minute SWR cache with intelligent deduplication
- ~15KB minified + gzipped bundle size
- Tree-shakable exports - import only what you need
- No layout shift - skeleton loaders maintain ad space
- Passive event listeners and efficient observers
🎨 Flexible UI & Theming
- Multiple built-in themes (light, dark, auto)
- Customizable skeleton variants (card, banner, minimal)
- CSS variable-based styling system
- Full support for custom ad renderers
- WCAG-compliant accessibility
- Mobile-first responsive design
🔧 Developer Experience
- Full TypeScript support with comprehensive type definitions
- React 18+ compatible
- Simple, intuitive API
- Comprehensive documentation
- Debug mode for development
- Built-in error handling
Installation
# npm
npm install @adsoverai/react
# pnpm
pnpm add @adsoverai/react
# yarn
yarn add @adsoverai/reactPeer Dependencies
The SDK requires React 18 or higher:
{
"react": ">=18.0.0",
"react-dom": ">=18.0.0"
}CDN Installation (No Build Step Required)
For quick prototyping, demos, or projects without a build system, you can use the SDK directly from a CDN:
Option 1: unpkg
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://unpkg.com/@adsoverai/[email protected]/dist/styles.css">
</head>
<body>
<div id="root"></div>
<!-- Load React -->
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<!-- Load AdsOverAI SDK -->
<script src="https://unpkg.com/@adsoverai/[email protected]/dist/index.global.js"></script>
<script>
const { AdsOverAIProvider, AdsOverAI } = window.AdsOverAI;
const App = () => (
React.createElement(AdsOverAIProvider, { apiKey: 'your-api-key' },
React.createElement(AdsOverAI, {
query: 'best laptop for programming',
response: 'I recommend laptops with good keyboards and powerful processors...'
})
)
);
ReactDOM.createRoot(document.getElementById('root')).render(
React.createElement(App)
);
</script>
</body>
</html>Option 2: jsDelivr (Faster Performance)
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@adsoverai/[email protected]/dist/styles.css">
</head>
<body>
<div id="root"></div>
<script crossorigin src="https://cdn.jsdelivr.net/npm/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://cdn.jsdelivr.net/npm/react-dom@18/umd/react-dom.production.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@adsoverai/[email protected]/dist/index.global.js"></script>
<script>
const { AdsOverAIProvider, AdsOverAI } = window.AdsOverAI;
const App = () => (
React.createElement(AdsOverAIProvider, { apiKey: 'your-api-key' },
React.createElement(AdsOverAI, {
query: 'best laptop for programming',
response: 'I recommend laptops with good keyboards...'
})
)
);
ReactDOM.createRoot(document.getElementById('root')).render(
React.createElement(App)
);
</script>
</body>
</html>Option 3: ESM (Modern Browsers)
For modern browsers with ES module support:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://unpkg.com/@adsoverai/[email protected]/dist/styles.css">
</head>
<body>
<div id="root"></div>
<script type="importmap">
{
"imports": {
"react": "https://esm.sh/react@18",
"react-dom": "https://esm.sh/react-dom@18",
"react-dom/client": "https://esm.sh/react-dom@18/client"
}
}
</script>
<script type="module">
import React from 'react';
import ReactDOM from 'react-dom/client';
import { AdsOverAIProvider, AdsOverAI } from 'https://unpkg.com/@adsoverai/[email protected]/dist/index.mjs';
const App = () => (
React.createElement(AdsOverAIProvider, { apiKey: 'your-api-key' },
React.createElement(AdsOverAI, {
query: 'best laptop for programming',
response: 'I recommend laptops with good keyboards and powerful processors...'
})
)
);
ReactDOM.createRoot(document.getElementById('root')).render(React.createElement(App));
</script>
</body>
</html>CDN Version Pinning
Always pin to a specific version in production to avoid unexpected breaking changes:
<!-- ✅ Recommended: Pinned to exact version -->
<script src="https://unpkg.com/@adsoverai/[email protected]/dist/index.global.js"></script>
<!-- ⚠️ Use with caution: Latest patch version -->
<script src="https://unpkg.com/@adsoverai/[email protected]/dist/index.global.js"></script>
<!-- ❌ Not recommended: Always latest (may include breaking changes) -->
<script src="https://unpkg.com/@adsoverai/react/dist/index.global.js"></script>CDN with Fallback
For production reliability, use a fallback CDN:
<script src="https://unpkg.com/@adsoverai/[email protected]/dist/index.global.js"
onerror="this.onerror=null; this.src='https://cdn.jsdelivr.net/npm/@adsoverai/[email protected]/dist/index.global.js'">
</script>Note: When using CDN, your API key will be visible in the browser source. Make sure to:
- Use a client-side API key (not a server secret)
- Enable domain restrictions in your AdsOverAI dashboard
- Monitor usage for unexpected traffic
Quick Start
Basic Integration
The simplest way to get started with AdsOverAI:
import { AdsOverAIProvider, AdsOverAI } from '@adsoverai/react';
import '@adsoverai/react/styles';
function App() {
return (
<AdsOverAIProvider apiKey="your-api-key">
<ChatInterface />
</AdsOverAIProvider>
);
}
function ChatInterface() {
const [messages, setMessages] = useState([
{ query: 'best running shoes', response: 'Here are some excellent options...' }
]);
return (
<div className="chat-container">
{messages.map((msg, i) => (
<div key={i}>
<div className="user-message">{msg.query}</div>
<div className="ai-response">{msg.response}</div>
{/* Display relevant ads */}
<AdsOverAI
query={msg.query}
response={msg.response}
maxAds={3}
theme="auto"
/>
</div>
))}
</div>
);
}With Next.js
App Router (Next.js 13+)
// app/layout.tsx
import { AdsOverAIProvider } from '@adsoverai/react';
import '@adsoverai/react/styles';
export default function RootLayout({ children }) {
return (
<html>
<body>
<AdsOverAIProvider
apiKey={process.env.NEXT_PUBLIC_ADSOVERAI_KEY}
impressionTracking={{
enabled: true,
viewportThreshold: 0.6,
visibilityDuration: 1500
}}
>
{children}
</AdsOverAIProvider>
</body>
</html>
);
}
// app/chat/page.tsx
'use client';
import { AdsOverAI } from '@adsoverai/react';
export default function ChatPage() {
return (
<AdsOverAI
query="travel destinations"
response="Consider these amazing places..."
onAdImpression={(ad, event) => {
console.log('Impression recorded:', ad.ad_id);
console.log('Trust score:', event.client_integrity?.trust_score);
}}
/>
);
}Pages Router (Next.js 12 and below)
// pages/_app.tsx
import { AdsOverAIProvider } from '@adsoverai/react';
import '@adsoverai/react/styles';
function MyApp({ Component, pageProps }) {
return (
<AdsOverAIProvider apiKey={process.env.NEXT_PUBLIC_ADSOVERAI_KEY}>
<Component {...pageProps} />
</AdsOverAIProvider>
);
}
export default MyApp;With TypeScript
Full type safety out of the box:
import type {
Ad,
AdsOverAIProps,
AdsOverAIProviderProps,
ImpressionConfig,
ImpressionEvent
} from '@adsoverai/react';
const impressionConfig: ImpressionConfig = {
enabled: true,
viewportThreshold: 0.5,
visibilityDuration: 1000
};
const handleImpression = (ad: Ad, event: ImpressionEvent): void => {
console.log(`Impression for ${ad.product_name}`);
console.log(`Trust score: ${event.client_integrity?.trust_score}`);
};Architecture
The SDK follows a provider-consumer pattern with intelligent caching and security layers:
graph LR
A[React App] --> B[AdsOverAIProvider]
B --> C[AdsOverAI Component]
C --> D[useAdsOverAI Hook]
D --> E[SWR Cache]
E --> F[AdsOverAI API]
C --> G[AdCard Component]
G --> H[ImpressionWrapper]
H --> I[useAdImpression Hook]
I --> J[IntersectionObserver]
I --> K[DOM Validator]
I --> L[Integrity Checker]
L --> M[Analytics API]
style B fill:#e3f2fd
style F fill:#fff3e0
style M fill:#f3e5f5Data Flow
- Initialization:
AdsOverAIProvidersets up global configuration and context - Ad Fetching:
useAdsOverAIhook fetches ads via SWR with intelligent caching - Rendering:
AdCardcomponents display ads with visual themes - Tracking:
ImpressionWrappermonitors viewport visibility via IntersectionObserver - Validation: DOM and integrity checks verify authentic viewing
- Recording: Verified impressions are sent to analytics API with cryptographic tokens
Core Concepts
Context-Aware Advertising
AdsOverAI uses AI-powered matching to display relevant ads based on conversation context:
<AdsOverAI
query="best laptop for programming"
response="I recommend laptops with good keyboards and powerful processors..."
/>The backend analyzes both the user's query and the AI's response to find the most relevant ads, considering:
- Semantic matching - Understanding intent beyond keywords
- Category relevance - Industry-specific ad targeting
- Confidence scoring - Only high-quality matches are shown
Verified Impression Tracking
Unlike traditional banner ads, our impression tracking ensures ads are actually seen:
<AdsOverAIProvider
impressionTracking={{
viewportThreshold: 0.5, // 50% of ad must be visible
visibilityDuration: 1000 // For at least 1 second
}}
/>What counts as an impression?
- Ad element is at least 50% visible in the viewport (configurable)
- Remains continuously visible for 1+ seconds (configurable)
- Not hidden by CSS (opacity, visibility, display)
- Has non-zero dimensions
- Passes runtime integrity checks
- Valid, non-expired impression token
Security & Fraud Prevention
Multi-layered security approach:
graph TD
A[Ad Served] -->|With Token| B[Token Verification]
B --> C[DOM Validation]
C --> D[Geometry Check]
D --> E[Integrity Score]
E --> F{Trust Score > 0.7?}
F -->|Yes| G[Record Impression]
F -->|No| H[Flag as Low Quality]
G --> I[Dedupe Check]
I -->|First Time| J[Success]
I -->|Replay| K[Reject]Security Layers:
- Cryptographic Tokens - Server-signed, single-use tokens
- DOM Validation - Checks for hidden/zero-size elements
- Runtime Integrity - Detects tampered browser APIs
- Trust Scoring - 0.0-1.0 confidence in impression authenticity
- Deduplication - Prevents replay attacks
- Rate Limiting - Backend behavioral analysis
Configuration
Provider Configuration
Configure global SDK behavior via <AdsOverAIProvider>:
<AdsOverAIProvider
apiKey="your-api-key" // Required: Your API key
maxAds={3} // Default: 3
theme="auto" // 'light' | 'dark' | 'auto'
skeletonVariant="card" // 'card' | 'banner' | 'minimal'
apiUrl="https://api.adsonai.com" // Custom API endpoint
debugMode={false} // Enable console logging
impressionTracking={{
enabled: true,
viewportThreshold: 0.5,
visibilityDuration: 1000
}}
>
{children}
</AdsOverAIProvider>Provider Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| apiKey | string | Required | Your AdsOverAI API key |
| maxAds | number | 3 | Maximum number of ads to display |
| theme | 'light' \| 'dark' \| 'auto' | 'auto' | Theme for ad display |
| skeletonVariant | 'card' \| 'banner' \| 'minimal' | 'card' | Loading skeleton style |
| apiUrl | string | 'https://api.adsonai.com' | API endpoint URL |
| debugMode | boolean | false | Enable debug logging |
| impressionTracking | ImpressionConfig | { enabled: true } | Impression tracking configuration |
Component Props
Override provider defaults per component:
<AdsOverAI
query="user search query" // Required
response="AI response text" // Required
maxAds={2} // Override provider default
theme="dark" // Override provider theme
skeletonVariant="banner" // Override skeleton
adPosition="bottom" // 'top' | 'bottom' | 'side'
className="my-ads" // Additional CSS classes
onAdClick={(ad) => track('click', ad)}
onAdLoad={(ads) => console.log(ads)}
onAdImpression={(ad, event) => {
analytics.track('impression', { ad, event })
}}
customAdRenderer={({ ads, ImpressionWrapper }) => (
// Custom rendering logic
)}
/>AdsOverAI Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| query | string | Required | User's search query or message |
| response | string | Required | AI's response text |
| maxAds | number | Provider default | Override max ads |
| theme | 'light' \| 'dark' \| 'auto' | Provider default | Override theme |
| skeletonVariant | 'card' \| 'banner' \| 'minimal' | Provider default | Override skeleton |
| adPosition | 'top' \| 'bottom' \| 'side' | 'bottom' | Position of ads |
| className | string | '' | Additional CSS classes |
| onAdClick | (ad: Ad) => void | undefined | Click event handler |
| onAdLoad | (ads: Ad[]) => void | undefined | Load event handler |
| onAdImpression | (ad: Ad, event: ImpressionEvent) => void | undefined | Impression event handler |
| customAdRenderer | Function | undefined | Custom rendering function |
Impression Tracking Configuration
Fine-tune viewability requirements:
<AdsOverAIProvider
impressionTracking={{
enabled: true, // Enable/disable tracking
viewportThreshold: 0.75, // 75% of ad must be visible
visibilityDuration: 2000 // Must be visible for 2 seconds
}}
/>Impression Config Options
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| enabled | boolean | true | Enable/disable impression tracking |
| viewportThreshold | number (0-1) | 0.5 | Percentage of ad that must be visible |
| visibilityDuration | number (ms) | 1000 | How long ad must remain visible |
Recommended Values:
- Standard Display:
{ viewportThreshold: 0.5, visibilityDuration: 1000 } - Premium Placements:
{ viewportThreshold: 0.75, visibilityDuration: 2000 } - Sidebar Ads:
{ viewportThreshold: 1.0, visibilityDuration: 1500 }
API Reference
Components
<AdsOverAIProvider>
Root component that provides SDK configuration via React Context.
import { AdsOverAIProvider } from '@adsoverai/react';
<AdsOverAIProvider apiKey="your-key" theme="dark">
<App />
</AdsOverAIProvider><AdsOverAI>
Main component for displaying ads alongside AI responses.
import { AdsOverAI } from '@adsoverai/react';
<AdsOverAI query="user query" response="AI response" /><AdCard>
Individual ad display component (usually used internally, but exportable for custom layouts).
import { AdCard } from '@adsoverai/react';
<AdCard ad={adObject} theme="light" onAdClick={handleClick} /><ImpressionWrapper>
Wraps ad content to enable impression tracking. Used in custom renderers.
import { ImpressionWrapper } from '@adsoverai/react';
<ImpressionWrapper ad={ad}>
<CustomAdComponent ad={ad} />
</ImpressionWrapper><AdSkeleton>
Loading placeholder component.
import { AdSkeleton } from '@adsoverai/react';
<AdSkeleton variant="card" count={3} />Ad Card Variations
The SDK includes four distinct ad card variations for different use cases:
<AdCardSimple>
Minimal design with only essential information - perfect for non-intrusive placements.
Features: Brand name, product name, "Sponsored" label
import { AdCardSimple } from '@adsoverai/react';
<AdCardSimple
ad={ad}
theme="light"
apiKey="your-api-key"
apiUrl="https://api.adsoverai.com"
onImpression={(ad, event) => console.log('Impression:', event)}
/><AdCardCTA>
Call-to-action focused with prominent CTA button - ideal for conversion-focused placements.
Features: Brand, product, description, price, large CTA button
import { AdCardCTA } from '@adsoverai/react';
<AdCardCTA ad={ad} theme="light" /><AdCardPreview>
Shows a preview/screenshot of the landing page - great for visual engagement.**
Features: Preview image (customizable size), brand, product, CTA button
import { AdCardPreview } from '@adsoverai/react';
<AdCardPreview
ad={ad}
theme="light"
previewConfig={{
width: 400,
height: 200,
showPreview: true
}}
/>Preview Image: Provide preview_image_url in your ad data:
const ad = {
// ... other properties
preview_image_url: "https://example.com/preview.jpg"
};<AdCardCategory>
Category-based theming with dynamic gradients and badges.
Features: Category badge, gradient background, category-specific colors
Predefined Themes: Sports & Fitness, Technology, Fashion, Food & Beverage, Travel, General
import { AdCardCategory } from '@adsoverai/react';
// Automatic theme based on ad.category
<AdCardCategory ad={ad} theme="light" />
// Custom category theme
<AdCardCategory
ad={ad}
theme="light"
categoryTheme={{
gradient: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
textColor: '#FFFFFF',
accentColor: '#FFD700'
}}
/>Visual Examples:
Simple Minimal Card
CTA-Focused Card
Landing Page Preview Card
Category-Based Card
Using with Custom Renderer:
<AdsOverAI
query="running shoes"
response="Looking for the best running shoes..."
customAdRenderer={({ ads, ImpressionWrapper }) => (
<div style={{ display: 'grid', gap: '20px' }}>
{ads.map((ad, index) => (
<ImpressionWrapper key={ad.ad_id} ad={ad}>
{index === 0 ? (
<AdCardCTA ad={ad} theme="light" />
) : ad.category === 'Sports & Fitness' ? (
<AdCardCategory ad={ad} theme="light" />
) : (
<AdCardSimple ad={ad} theme="light" />
)}
</ImpressionWrapper>
))}
</div>
)}
/>📚 Complete Documentation: See AD_CARD_VARIATIONS.md for detailed API reference, all category themes, and more examples.
🎨 Live Demo: View
examples/ad-card-variations-demo.htmlto see all variations in action.
useAdsOverAI
Fetch ads programmatically with SWR caching.
import { useAdsOverAI } from '@adsoverai/react';
function MyComponent() {
const { ads, loading, error, refetch } = useAdsOverAI({
query: 'laptop recommendations',
response: 'Here are some options...',
maxAds: 3,
apiKey: 'your-api-key',
apiUrl: 'https://api.adsonai.com',
enabled: true,
});
if (loading) return <div>Loading ads...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
{ads.map(ad => (
<div key={ad.ad_id}>{ad.product_name}</div>
))}
<button onClick={refetch}>Refresh Ads</button>
</div>
);
}Returns:
ads:Ad[]- Array of ad objectsloading:boolean- Loading stateerror:Error | undefined- Error object if failedrefetch:() => void- Manually refetch ads
useAdsOverAIContext
Access provider context values.
import { useAdsOverAIContext } from '@adsoverai/react';
function MyComponent() {
const {
apiKey,
theme,
maxAds,
impressionTracking,
debugMode
} = useAdsOverAIContext();
return <div>Current theme: {theme}</div>;
}Returns:
apiKey:stringmaxAds:numbertheme:'light' | 'dark' | 'auto'skeletonVariant:'card' | 'banner' | 'minimal'apiUrl:stringdebugMode:booleanimpressionTracking:ImpressionConfig
useAdImpression
Low-level hook for custom impression tracking (advanced usage).
import { useAdImpression } from '@adsoverai/react';
function CustomAdComponent({ ad }) {
const adRef = useAdImpression({
ad,
onImpression: (ad, event) => {
console.log('Impression recorded!', ad, event);
},
config: {
enabled: true,
viewportThreshold: 0.5,
visibilityDuration: 1000
}
});
return <div ref={adRef}>Ad content</div>;
}TypeScript Types
Ad
interface Ad {
ad_id: string;
brand_name: string;
product_name: string;
description: string;
ad_text?: string;
cta_text?: string;
cta_link?: string;
landing_url?: string;
category?: string;
price_range?: string;
relevanceScore?: number;
confidence?: number;
matching_metadata?: {
relevance_score: number;
intent_score: number;
semantic_score: number;
confidence_level: number;
ai_reason?: string;
};
impression_token?: string;
}ImpressionConfig
interface ImpressionConfig {
enabled: boolean;
viewportThreshold: number; // 0.0 - 1.0
visibilityDuration: number; // milliseconds
}ImpressionEvent
interface ImpressionEvent {
ad_id: string;
impression_token: string;
timestamp: string;
viewport_threshold: number;
visibility_duration: number;
screen_dimensions: { width: number; height: number };
user_agent: string;
client_integrity?: {
is_native_observer: boolean;
is_native_timer: boolean;
trust_score: number; // 0.0 - 1.0
};
}AdsOverAIProviderProps
interface AdsOverAIProviderProps {
apiKey: string;
maxAds?: number;
theme?: 'light' | 'dark' | 'auto';
skeletonVariant?: 'card' | 'banner' | 'minimal';
apiUrl?: string;
debugMode?: boolean;
impressionTracking?: Partial<ImpressionConfig>;
children: React.ReactNode;
}AdsOverAIProps
interface AdsOverAIProps {
query: string;
response: string;
maxAds?: number;
theme?: 'light' | 'dark' | 'auto';
skeletonVariant?: 'card' | 'banner' | 'minimal';
adPosition?: 'top' | 'bottom' | 'side';
className?: string;
onAdClick?: (ad: Ad) => void;
onAdLoad?: (ads: Ad[]) => void;
onAdImpression?: (ad: Ad, event: ImpressionEvent) => void;
customAdRenderer?: ((ads: Ad[]) => React.ReactNode) | ((props: {
ads: Ad[];
ImpressionWrapper: React.ComponentType<{ ad: Ad; children: React.ReactNode }>;
}) => React.ReactNode);
}Utilities
sendImpressionEvent
Send impression event to analytics API (used internally, but exportable).
import { sendImpressionEvent } from '@adsoverai/react';
await sendImpressionEvent(
impressionEvent, // ImpressionEvent object
apiKey, // Your API key
apiUrl // API endpoint URL
);Advanced Usage
Custom Ad Rendering
Create fully custom ad layouts while maintaining impression tracking:
<AdsOverAI
query={query}
response={response}
customAdRenderer={({ ads, ImpressionWrapper }) => (
<div className="my-custom-grid">
{ads.map(ad => (
<ImpressionWrapper key={ad.ad_id} ad={ad}>
<article className="custom-ad-card">
<header>
<img src={ad.brand_logo} alt={ad.brand_name} />
<h3>{ad.brand_name}</h3>
</header>
<div className="content">
<h4>{ad.product_name}</h4>
<p>{ad.description}</p>
{ad.price_range && <span className="price">{ad.price_range}</span>}
</div>
<footer>
<a
href={ad.cta_link}
target="_blank"
rel="noopener noreferrer"
className="cta-button"
>
{ad.cta_text || 'Learn More'}
</a>
</footer>
</article>
</ImpressionWrapper>
))}
</div>
)}
/>Important: Always wrap each ad with <ImpressionWrapper> to enable tracking! Without it, impressions won't be recorded.
Using ImpressionWrapper Independently
import { ImpressionWrapper, useAdsOverAI } from '@adsoverai/react';
function CustomAdDisplay() {
const { ads } = useAdsOverAI({ query: '...', response: '...' });
return (
<div className="ad-grid">
{ads.map(ad => (
<ImpressionWrapper key={ad.ad_id} ad={ad}>
<YourCustomAdComponent ad={ad} />
</ImpressionWrapper>
))}
</div>
);
}Legacy Custom Renderer (No Tracking)
The old signature is still supported but won't track impressions:
// ⚠️ This works but won't track impressions
customAdRenderer={(ads) => (
<div>
{ads.map(ad => (
<div key={ad.ad_id}>{ad.product_name}</div>
))}
</div>
)}Event Tracking & Analytics
Track all ad interactions for your analytics platform:
<AdsOverAI
query={query}
response={response}
onAdLoad={(ads) => {
// Track ad delivery
analytics.track('ads_loaded', {
count: ads.length,
query: query,
adIds: ads.map(a => a.ad_id)
});
}}
onAdClick={(ad) => {
// Track clicks
analytics.track('ad_clicked', {
adId: ad.ad_id,
brandName: ad.brand_name,
productName: ad.product_name,
ctaLink: ad.cta_link
});
}}
onAdImpression={(ad, event) => {
// Track verified impressions
analytics.track('ad_impression', {
adId: ad.ad_id,
brandName: ad.brand_name,
productName: ad.product_name,
trustScore: event.client_integrity?.trust_score,
viewportThreshold: event.viewport_threshold,
visibilityDuration: event.visibility_duration,
timestamp: event.timestamp,
isNativeObserver: event.client_integrity?.is_native_observer,
screenSize: event.screen_dimensions
});
// Optional: Send to your own backend
fetch('/api/track-impression', {
method: 'POST',
body: JSON.stringify({ ad, event })
});
}}
/>Custom Styling
Override default styles using CSS variables:
/* Light theme customization */
.adsoverai-message {
--adsoverai-bg: #f9f9f9;
--adsoverai-text: #333;
--adsoverai-border: #ddd;
--adsoverai-border-radius: 12px;
--adsoverai-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
/* Dark theme customization */
[data-theme='dark'] .adsoverai-message {
--adsoverai-bg: #1a1a1a;
--adsoverai-text: #ffffff;
--adsoverai-border: #404040;
--adsoverai-shadow: 0 2px 8px rgba(0,0,0,0.3);
}
/* Button customization */
.adsoverai-cta {
--adsoverai-button-bg: #007bff;
--adsoverai-button-hover: #0056b3;
--adsoverai-button-text: #ffffff;
}
/* Custom ad container */
.my-custom-ads .adsoverai-message {
padding: 24px;
margin: 16px 0;
}Available CSS Variables
--adsoverai-bg- Background color--adsoverai-text- Text color--adsoverai-border- Border color--adsoverai-border-radius- Border radius--adsoverai-shadow- Box shadow--adsoverai-button-bg- CTA button background--adsoverai-button-hover- CTA button hover state--adsoverai-button-text- CTA button text color
Impression Tracking Deep Dive
How Impression Tracking Works
Our impression tracking ensures advertisers only pay for ads that users actually see:
graph TD
A[Ad Renders] -->|Off Screen| B(Idle State)
B -->|User Scrolls| C{50% Visible?}
C -->|No| B
C -->|Yes| D[Start Timer]
D -->|Wait 1s| E{Still Visible?}
E -->|No| F[Cancel Timer]
F --> B
E -->|Yes| G[Validate DOM State]
G -->|Check opacity, visibility, display| H[Check Element Geometry]
H -->|Non-zero width/height| I[Runtime Integrity Check]
I -->|Verify native APIs| J[Calculate Trust Score]
J -->|Score 0.0-1.0| K[Fire Impression Event]
K -->|Send with Token| L[Backend Verification]
L -->|Verify signature| M{Valid Token?}
M -->|Yes| N[Check Deduplication]
N -->|First Use| O[Record Impression ✅]
N -->|Replay| P[Reject - Already Used ❌]
M -->|Invalid/Expired| Q[Reject - Bad Token ❌]Impression Flow Step-by-Step
- Ad Renders: Component mounts and IntersectionObserver is initialized
- Viewport Detection: Observer detects when ad enters viewport
- Threshold Check: Verifies if configured percentage is visible (default 50%)
- Timer Start: Starts countdown for visibility duration (default 1000ms)
- Continuous Monitoring: Checks if ad stays visible during duration
- DOM Validation: Before recording, validates:
- Element has
opacity > 0 - Element has
visibility !== 'hidden' - Element has
display !== 'none' - Element has non-zero
widthandheight - Element is attached to
document.body
- Element has
- Integrity Check: Verifies browser environment:
- IntersectionObserver is native (not mocked)
- setTimeout is native (not tampered)
- Not running in webdriver/bot
- HTTPS secure context
- Trust Score: Calculates 0.0-1.0 confidence score
- Event Emission: Fires
onAdImpressioncallback - API Request: Sends impression event with cryptographic token
- Backend Verification: Server verifies token signature and validity
- Deduplication: Ensures token hasn't been used before
- Recording: Impression is recorded (or rejected if invalid)
Impression Tracking Options
Configure viewability requirements globally:
<AdsOverAIProvider
impressionTracking={{
enabled: true, // Master switch
viewportThreshold: 0.5, // 50% visible (0.0 - 1.0)
visibilityDuration: 1000 // 1 second (milliseconds)
}}
/>Configuration Guidelines
| Use Case | Threshold | Duration | Rationale | |----------|-----------|----------|-----------| | Standard Display Ads | 0.5 | 1000ms | IAB standard for display advertising | | Premium Placements | 0.75 | 2000ms | Higher confidence of user attention | | Sidebar/Persistent Ads | 1.0 | 1500ms | Ensure complete visibility | | Mobile/Fast Scroll | 0.5 | 500ms | Adjust for quick scrolling patterns | | Video Ads | 0.5 | 2000ms | Longer engagement requirement |
Listening to Impression Events
<AdsOverAI
query={query}
response={response}
onAdImpression={(ad, event) => {
console.log('✅ Valid impression recorded');
console.log('Ad:', ad.product_name);
console.log('Trust score:', event.client_integrity?.trust_score);
console.log('Viewport threshold met:', event.viewport_threshold);
console.log('Visibility duration:', event.visibility_duration);
console.log('Screen size:', event.screen_dimensions);
console.log('Native observer:', event.client_integrity?.is_native_observer);
console.log('Timestamp:', event.timestamp);
// Send to your analytics
analytics.track('ad_impression', {
adId: ad.ad_id,
trustScore: event.client_integrity?.trust_score,
viewabilityMet: event.viewport_threshold >= 0.5
});
}}
/>Security Features
1. Cryptographic Tokens
Each ad includes a server-signed impression_token:
{
"ad_id": "ad_12345",
"impression_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}The token encodes:
ad_id- Which ad was servedapplication_id- Which app requested itissued_at- When token was createdexpires_at- Token expiration timesignature- Cryptographic signature
Backend verifies the signature before recording any impression.
2. DOM Validation
Before recording impression, we check:
// Pseudo-code of validation checks
const computedStyle = window.getComputedStyle(element);
const rect = element.getBoundingClientRect();
const isValid =
computedStyle.opacity !== '0' &&
computedStyle.visibility !== 'hidden' &&
computedStyle.display !== 'none' &&
rect.width > 0 &&
rect.height > 0 &&
document.body.contains(element);This prevents "hidden ad fraud" where ads are loaded but not actually visible.
3. Runtime Integrity Checks
We detect tampered browser environments:
// Check if IntersectionObserver is native
const isNativeObserver =
IntersectionObserver.toString().includes('[native code]');
// Check if setTimeout is native
const isNativeTimer =
setTimeout.toString().includes('[native code]');
// Detect webdriver/automation
const isWebdriver = navigator.webdriver === true;
// Verify HTTPS context
const isSecureContext = window.isSecureContext === true;
// Calculate trust score (0.0 - 1.0)
const trustScore = calculateTrustScore({
isNativeObserver,
isNativeTimer,
isWebdriver,
isSecureContext
});Trust scores are sent with each impression and used by the backend for quality filtering.
4. Deduplication
Each impression_token can only be used once:
Client sends: impression_token = "abc123..."
Server checks: Has "abc123..." been used?
→ NO: Record impression ✅
→ YES: Reject as replay attack ❌5. Behavioral Analysis (Backend)
The backend analyzes patterns:
- Rate limiting - Too many impressions from one IP/device
- Timing patterns - Suspiciously consistent visibility durations
- Trust score filtering - Low trust scores flagged for review
- Geographic validation - Cross-reference with expected user locations
Examples
Basic Chat Integration
import { useState } from 'react';
import { AdsOverAIProvider, AdsOverAI } from '@adsoverai/react';
import '@adsoverai/react/styles';
function ChatApp() {
const [messages, setMessages] = useState([
{
id: 1,
query: 'best gaming laptops',
response: 'Here are top picks for gaming laptops in 2024...'
}
]);
return (
<AdsOverAIProvider
apiKey="your-api-key"
theme="auto"
impressionTracking={{
enabled: true,
viewportThreshold: 0.6,
visibilityDuration: 1500
}}
>
<div className="chat-container">
{messages.map((msg) => (
<div key={msg.id} className="message-group">
<div className="user-query">{msg.query}</div>
<div className="ai-response">{msg.response}</div>
<AdsOverAI
query={msg.query}
response={msg.response}
maxAds={3}
onAdImpression={(ad, event) => {
console.log(`Impression: ${ad.product_name}`);
console.log(`Trust: ${event.client_integrity?.trust_score}`);
}}
/>
</div>
))}
</div>
</AdsOverAIProvider>
);
}E-commerce Search Integration
import { AdsOverAI } from '@adsoverai/react';
function SearchResults({ query, results }) {
const resultsText = results
.map(r => `${r.name}: ${r.description}`)
.join('. ');
return (
<div className="search-page">
<h1>Search Results for "{query}"</h1>
{/* Display search results */}
<div className="results-grid">
{results.map(result => (
<ProductCard key={result.id} product={result} />
))}
</div>
{/* Display relevant ads */}
<AdsOverAI
query={query}
response={resultsText}
maxAds={4}
adPosition="side"
theme="light"
/>
</div>
);
}Customer Support Bot
import { AdsOverAI } from '@adsoverai/react';
function SupportChat({ conversation }) {
return (
<div className="support-chat">
{conversation.map((turn, idx) => (
<div key={idx}>
<div className="customer-message">{turn.customerMessage}</div>
<div className="bot-response">{turn.botResponse}</div>
{/* Show ads for product recommendations */}
{turn.intent === 'product_inquiry' && (
<AdsOverAI
query={turn.customerMessage}
response={turn.botResponse}
maxAds={2}
theme="auto"
onAdClick={(ad) => {
// Track click-through from support chat
analytics.track('support_ad_click', {
conversationId: conversation.id,
adId: ad.ad_id,
intent: turn.intent
});
}}
/>
)}
</div>
))}
</div>
);
}Multi-Language Support
import { AdsOverAI } from '@adsoverai/react';
function MultiLangChat({ language, query, response }) {
return (
<AdsOverAI
query={query}
response={response}
// Ads will be matched based on content language
onAdLoad={(ads) => {
console.log(`Loaded ${ads.length} ads for ${language}`);
}}
/>
);
}Custom Analytics Integration
import { AdsOverAI } from '@adsoverai/react';
function AnalyticsExample() {
const trackEvent = (eventName, properties) => {
// Send to your analytics platform
window.analytics.track(eventName, properties);
// Send to your backend
fetch('/api/analytics', {
method: 'POST',
body: JSON.stringify({ event: eventName, ...properties })
});
};
return (
<AdsOverAI
query="best CRM software"
response="Here are some popular CRM solutions..."
onAdLoad={(ads) => {
trackEvent('ads_rendered', {
count: ads.length,
adIds: ads.map(a => a.ad_id),
brands: ads.map(a => a.brand_name)
});
}}
onAdClick={(ad) => {
trackEvent('ad_clicked', {
adId: ad.ad_id,
brand: ad.brand_name,
product: ad.product_name,
ctaText: ad.cta_text
});
}}
onAdImpression={(ad, event) => {
trackEvent('ad_impression', {
adId: ad.ad_id,
brand: ad.brand_name,
trustScore: event.client_integrity?.trust_score,
viewportThreshold: event.viewport_threshold,
visibilityMs: event.visibility_duration,
isNative: event.client_integrity?.is_native_observer
});
}}
/>
);
}Troubleshooting
Ads Not Showing
Problem: No ads are displayed, only skeleton or nothing.
Solutions:
Verify API Key
// Enable debug mode to see console logs <AdsOverAIProvider apiKey="your-key" debugMode={true}>Check Network Requests
- Open DevTools → Network tab
- Look for requests to your API endpoint
- Check response status and body
Verify Query Content
// Query should have meaningful content <AdsOverAI query="laptop" // ❌ Too short query="I need a laptop for video editing" // ✅ Good />Check for Errors
const { ads, error } = useAdsOverAI({ ... }); if (error) console.error('Ad fetch error:', error);
Impressions Not Tracking
Problem: onAdImpression callback never fires.
Solutions:
Using Custom Renderer? Wrap with ImpressionWrapper!
// ❌ Wrong - no impression tracking customAdRenderer={(ads) => ( <div>{ads.map(ad => <div>{ad.product_name}</div>)}</div> )} // ✅ Correct - with ImpressionWrapper customAdRenderer={({ ads, ImpressionWrapper }) => ( <div> {ads.map(ad => ( <ImpressionWrapper key={ad.ad_id} ad={ad}> <div>{ad.product_name}</div> </ImpressionWrapper> ))} </div> )}Verify Impression Tracking is Enabled
<AdsOverAIProvider impressionTracking={{ enabled: true }} // Make sure this is true />Check Console Logs
[AdsOverAI] Recording impression for ad: ad_12345 ✅ Impression sent to backend: imp_67890If you see:
[AdsOverAI] Skipping impression - missing required dataCheck that ads have
impression_tokenfrom backend.Verify Ads Are Actually Visible
- Not hidden by
display: none,opacity: 0,visibility: hidden - Have non-zero width and height
- Are scrolled into viewport
- Stay visible for required duration (default 1 second)
- Not hidden by
Check Provider Configuration
<AdsOverAIProvider apiKey="your-api-key" // ← Required apiUrl="https://www.adsoverai.com" // ← Required impressionTracking={{ enabled: true, viewportThreshold: 0.5, visibilityDuration: 1000 }} />
TypeScript Errors
Problem: Type errors when using the SDK.
Solutions:
Install Type Definitions
npm install --save-dev @types/react @types/react-domVerify TypeScript Configuration
// tsconfig.json { "compilerOptions": { "jsx": "react-jsx", "esModuleInterop": true, "skipLibCheck": true } }Import Types Explicitly
import type { Ad, ImpressionEvent } from '@adsoverai/react';
Styling Conflicts
Problem: SDK styles conflict with your app's CSS.
Solutions:
Import SDK Styles Last
import './app.css'; import '@adsoverai/react/styles'; // ← Import after your CSSUse CSS Specificity
/* Your overrides */ .my-app .adsoverai-message { background: custom-color; }CSS Variables
.adsoverai-message { --adsoverai-bg: your-color; }
Next.js Build Errors
Problem: Errors during Next.js build.
Solutions:
Mark Component as Client-Side
'use client'; // ← Add this at top of file import { AdsOverAI } from '@adsoverai/react';Dynamic Import (Pages Router)
import dynamic from 'next/dynamic'; const AdsOverAI = dynamic( () => import('@adsoverai/react').then(mod => mod.AdsOverAI), { ssr: false } );
Performance Issues
Problem: Ads loading slowly or causing lag.
Solutions:
Leverage SWR Caching
// Ads are cached for 5 minutes by default // Subsequent renders with same query are instantLimit Max Ads
<AdsOverAI maxAds={2} /> // Show fewer adsUse Skeleton Variants Wisely
// Minimal skeleton for faster perceived load <AdsOverAIProvider skeletonVariant="minimal">Optimize Custom Renderers
// Avoid heavy computations in custom renderer customAdRenderer={React.memo(({ ads, ImpressionWrapper }) => ( // Memoized rendering logic ))}
Performance
Bundle Size
- Core SDK: ~15KB gzipped
- Styles: ~3KB gzipped
- Total: ~18KB gzipped
The SDK is tree-shakable - import only what you need:
// Import specific components
import { AdsOverAI } from '@adsoverai/react'; // Smaller bundle
// vs importing everything
import * as AdsOverAI from '@adsoverai/react'; // Larger bundleCaching Strategy
SWR-Powered Intelligent Caching:
// First render - fetches from API
<AdsOverAI query="laptops" response="..." />
// Second render with same query - instant from cache
<AdsOverAI query="laptops" response="..." />
// Cache expires after 5 minutes, then auto-revalidatesBenefits:
- ⚡ Zero-latency for cached queries
- 🔄 Automatic background revalidation
- 🎯 Request deduplication (multiple components = one request)
- 💾 Reduced API calls = lower costs
Rendering Optimizations
Skeleton Loaders:
- Prevent layout shift
- Maintain UI space while loading
- Smooth transition to actual ads
Lazy Intersection Observer:
- Only tracks ads when near viewport
- Efficient passive event listeners
- Automatic cleanup on unmount
CSS Performance:
- Hardware-accelerated animations
- Minimal repaints
- Optimized pseudo-selectors
Browser Support
Supported Browsers
- ✅ Chrome/Edge: Latest 2 versions
- ✅ Firefox: Latest 2 versions
- ✅ Safari: Latest 2 versions (including iOS Safari)
- ✅ Samsung Internet: Latest version
- ✅ Chrome Android: Latest 2 versions
Required APIs
The SDK requires modern browser APIs:
- IntersectionObserver - For viewport detection (available in all modern browsers)
- Promises - For async operations
- fetch - For API requests
- Web Crypto API - For security features (optional, degrades gracefully)
Polyfills
If you need to support older browsers:
npm install intersection-observer// In your app entry point
import 'intersection-observer';
import { AdsOverAIProvider } from '@adsoverai/react';Contributing
We welcome contributions! Here's how you can help:
Development Setup
# Clone the repository
git clone https://github.com/adsoverai/react-sdk.git
cd react-sdk
# Install dependencies
npm install
# Run development build
npm run dev
# Run tests
npm test
# Run tests with UI
npm run test:ui
# Type checking
npm run typecheck
# Linting
npm run lint
npm run lint:fix
# Format code
npm run formatProject Structure
src/
├── components/ # React components
│ ├── AdsOverAI.tsx # Main ad display component
│ ├── AdsOverAIProvider.tsx # Context provider
│ ├── AdCard.tsx # Individual ad card
│ ├── ImpressionWrapper.tsx # Impression tracking wrapper
│ └── AdSkeleton.tsx # Loading skeletons
├── hooks/ # Custom React hooks
│ ├── useAdsOverAI.ts # Ad fetching hook
│ └── useAdImpression.ts # Impression tracking hook
├── utils/ # Utility functions
│ ├── analytics.ts # Analytics/impression API
│ ├── security.ts # Security & integrity checks
│ └── validation.ts # DOM validation
├── types/ # TypeScript types
│ └── index.ts # Type definitions
├── styles/ # CSS styles
│ └── index.css # Component styles
└── index.ts # Main entry pointGuidelines
- Code Style: We use Biome for linting and formatting
- Tests: Add tests for new features
- Types: Maintain full TypeScript coverage
- Documentation: Update README and JSDoc comments
- Commits: Use conventional commit messages
Pull Request Process
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Run tests and linting (
npm run test && npm run lint) - Commit your changes (
git commit -m 'feat: add amazing feature') - Push to your fork (
git push origin feature/amazing-feature) - Open a Pull Request
Documentation
Additional Resources
- 📖 Impression Tracking Deep Dive - Detailed architecture and security
- 🔄 SDK Architecture Flow - Data flow diagrams
- 📝 Changelog - Version history and updates
- 🌐 Official Website - Product information
- 📚 Full Documentation - Complete guides
Related Packages
@adsoverai/vue- Vue.js SDK (coming soon)@adsoverai/angular- Angular SDK (coming soon)@adsoverai/vanilla- Vanilla JavaScript SDK (coming soon)
Support
Need help? We're here for you:
Get in Touch
- 📧 Email: [email protected]
- 🌐 Website: https://www.adsoverai.com
- 📚 Documentation: https://docs.adsoverai.com
- 🐛 Bug Reports: GitHub Issues
- 💬 Discussions: GitHub Discussions
Commercial Support
For enterprise support, custom integrations, or priority bug fixes:
- Email: [email protected]
- Website: https://www.adsoverai.com/enterprise
License
MIT © AdsOverAI Team
See LICENSE for more information.
Acknowledgments
Built with:
- ⚛️ React
- 📘 TypeScript
- 🎨 CSS3
- 📦 tsup
- 🧪 Vitest
- 🌐 SWR
Special thanks to all our contributors and the open-source community!
Made with ❤️ by the AdsOverAI Team
⭐ Star us on GitHub • 🐦 Follow us on Twitter • 📰 Read our Blog
