@mixpeek/prebid 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +153 -0
- package/ENDPOINTS.md +308 -0
- package/LICENSE +68 -0
- package/QUICKSTART.md +234 -0
- package/README.md +439 -0
- package/TESTING.md +341 -0
- package/dist/mixpeekContextAdapter.js +3 -0
- package/dist/mixpeekContextAdapter.js.LICENSE.txt +1 -0
- package/dist/mixpeekContextAdapter.js.map +1 -0
- package/docs/MIGRATION_V2.md +519 -0
- package/docs/api-reference.md +455 -0
- package/docs/health-check.md +348 -0
- package/docs/integration-guide.md +577 -0
- package/examples/publisher-demo/README.md +65 -0
- package/examples/publisher-demo/index.html +331 -0
- package/examples/publisher-demo/package.json +11 -0
- package/package.json +82 -0
- package/src/api/mixpeekClient.js +303 -0
- package/src/cache/cacheManager.js +245 -0
- package/src/config/constants.js +125 -0
- package/src/extractors/imageExtractor.js +142 -0
- package/src/extractors/pageExtractor.js +196 -0
- package/src/extractors/videoExtractor.js +228 -0
- package/src/modules/mixpeekContextAdapter.js +833 -0
- package/src/modules/mixpeekRtdProvider.js +305 -0
- package/src/prebid/prebidIntegration.js +117 -0
- package/src/utils/helpers.js +261 -0
- package/src/utils/iabMapping.js +367 -0
- package/src/utils/logger.js +64 -0
- package/src/utils/previousAdTracker.js +95 -0
package/QUICKSTART.md
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# Quick Start Guide
|
|
2
|
+
|
|
3
|
+
Get the Mixpeek Context Adapter running in 5 minutes.
|
|
4
|
+
|
|
5
|
+
## 1. Get Your API Key
|
|
6
|
+
|
|
7
|
+
1. Sign up at [mixpeek.com/start](https://mixpeek.com/start)
|
|
8
|
+
2. Navigate to your dashboard
|
|
9
|
+
3. Generate an API key (starts with `sk_`)
|
|
10
|
+
4. Copy the API key
|
|
11
|
+
|
|
12
|
+
## 2. Set Up Environment
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# Clone or navigate to the repository
|
|
16
|
+
cd /path/to/prebid.js
|
|
17
|
+
|
|
18
|
+
# Install dependencies
|
|
19
|
+
npm install
|
|
20
|
+
|
|
21
|
+
# Set your API credentials
|
|
22
|
+
export MIXPEEK_API_KEY="sk_your_api_key_here"
|
|
23
|
+
|
|
24
|
+
# Use development server (temporary)
|
|
25
|
+
export MIXPEEK_API_ENDPOINT="https://server-xb24.onrender.com"
|
|
26
|
+
|
|
27
|
+
# Optional: Set a collection ID (will create if not set)
|
|
28
|
+
export MIXPEEK_COLLECTION_ID="col_your_collection_id"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
> **Note**: We're currently using the development server at `https://server-xb24.onrender.com`. When ready for production, change to `https://api.mixpeek.com`.
|
|
32
|
+
|
|
33
|
+
## 3. Validate Setup
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm run validate
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Expected output:
|
|
40
|
+
```
|
|
41
|
+
🔍 Mixpeek API Setup Validation
|
|
42
|
+
|
|
43
|
+
================================
|
|
44
|
+
|
|
45
|
+
📋 Configuration Check:
|
|
46
|
+
API Key: ✅ Set
|
|
47
|
+
Collection ID: ⚠️ Not set (will create)
|
|
48
|
+
Namespace: ⚠️ Not set (using default)
|
|
49
|
+
Endpoint: https://api.mixpeek.com
|
|
50
|
+
|
|
51
|
+
🌐 Testing API Connection...
|
|
52
|
+
|
|
53
|
+
✅ API Connection: Success
|
|
54
|
+
Status: healthy
|
|
55
|
+
|
|
56
|
+
🧩 Testing Feature Extractors Endpoint...
|
|
57
|
+
|
|
58
|
+
✅ Feature Extractors: Available
|
|
59
|
+
Found 12 extractors
|
|
60
|
+
Available: taxonomy, brand-safety, keywords, sentiment, clustering
|
|
61
|
+
|
|
62
|
+
================================
|
|
63
|
+
|
|
64
|
+
✅ Setup validation complete!
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## 4. Run Tests
|
|
68
|
+
|
|
69
|
+
### Unit Tests (No API Required)
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
npm test
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Live API Tests
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npm run test:live
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### All Tests
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
npm run test:all
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## 5. Build the Adapter
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
npm run build
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Output: `dist/mixpeekContextAdapter.js`
|
|
94
|
+
|
|
95
|
+
## 6. Try the Example
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
# Update the example with your credentials
|
|
99
|
+
# Edit: examples/publisher-demo/index.html
|
|
100
|
+
|
|
101
|
+
# Run the demo
|
|
102
|
+
cd examples/publisher-demo
|
|
103
|
+
npm install
|
|
104
|
+
npm start
|
|
105
|
+
|
|
106
|
+
# Open http://localhost:8080
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## 7. Integrate with Your Site
|
|
110
|
+
|
|
111
|
+
### Basic Integration
|
|
112
|
+
|
|
113
|
+
```html
|
|
114
|
+
<!-- Load Prebid.js -->
|
|
115
|
+
<script src="https://cdn.jsdelivr.net/npm/prebid.js@latest/dist/prebid.js"></script>
|
|
116
|
+
|
|
117
|
+
<!-- Load Mixpeek Adapter -->
|
|
118
|
+
<script src="path/to/mixpeekContextAdapter.js"></script>
|
|
119
|
+
|
|
120
|
+
<script>
|
|
121
|
+
var pbjs = pbjs || {};
|
|
122
|
+
pbjs.que = pbjs.que || [];
|
|
123
|
+
|
|
124
|
+
pbjs.que.push(function() {
|
|
125
|
+
// Configure Mixpeek as an RTD provider
|
|
126
|
+
pbjs.setConfig({
|
|
127
|
+
realTimeData: {
|
|
128
|
+
auctionDelay: 250, // Max time to wait for contextual data
|
|
129
|
+
dataProviders: [{
|
|
130
|
+
name: 'mixpeek',
|
|
131
|
+
waitForIt: true, // Wait for Mixpeek before starting auction
|
|
132
|
+
params: {
|
|
133
|
+
apiKey: 'YOUR_API_KEY',
|
|
134
|
+
collectionId: 'YOUR_COLLECTION_ID',
|
|
135
|
+
endpoint: 'https://server-xb24.onrender.com', // Development server
|
|
136
|
+
featureExtractors: ['taxonomy'],
|
|
137
|
+
mode: 'auto',
|
|
138
|
+
timeout: 5000, // Higher timeout for dev server
|
|
139
|
+
cacheTTL: 300
|
|
140
|
+
}
|
|
141
|
+
}]
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Add your ad units
|
|
146
|
+
pbjs.addAdUnits([
|
|
147
|
+
{
|
|
148
|
+
code: 'div-banner-1',
|
|
149
|
+
mediaTypes: {
|
|
150
|
+
banner: { sizes: [[300, 250]] }
|
|
151
|
+
},
|
|
152
|
+
bids: [
|
|
153
|
+
{
|
|
154
|
+
bidder: 'rubicon',
|
|
155
|
+
params: { /* ... */ }
|
|
156
|
+
}
|
|
157
|
+
]
|
|
158
|
+
}
|
|
159
|
+
]);
|
|
160
|
+
|
|
161
|
+
// Request bids
|
|
162
|
+
pbjs.requestBids({
|
|
163
|
+
bidsBackHandler: function(bids) {
|
|
164
|
+
// Your bid handling code
|
|
165
|
+
// Bids now include Mixpeek contextual data in ortb2
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
</script>
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Troubleshooting
|
|
173
|
+
|
|
174
|
+
### "API key not set"
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
export MIXPEEK_API_KEY="sk_your_key"
|
|
178
|
+
npm run validate
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### "Collection not found"
|
|
182
|
+
|
|
183
|
+
Create a collection:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
curl -X POST https://api.mixpeek.com/v1/collections \
|
|
187
|
+
-H "Authorization: Bearer $MIXPEEK_API_KEY" \
|
|
188
|
+
-H "Content-Type: application/json" \
|
|
189
|
+
-d '{
|
|
190
|
+
"name": "prebid-contextual",
|
|
191
|
+
"description": "Contextual targeting for Prebid"
|
|
192
|
+
}'
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Save the `collection_id` and export it:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
export MIXPEEK_COLLECTION_ID="col_abc123"
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Tests Timing Out
|
|
202
|
+
|
|
203
|
+
Increase timeout in `jest.config.live.js`:
|
|
204
|
+
|
|
205
|
+
```javascript
|
|
206
|
+
testTimeout: 60000 // 60 seconds
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Next Steps
|
|
210
|
+
|
|
211
|
+
- 📚 [Integration Guide](docs/integration-guide.md) - Detailed integration instructions
|
|
212
|
+
- 📖 [API Reference](docs/api-reference.md) - Complete API documentation
|
|
213
|
+
- 🧪 [Testing Guide](TESTING.md) - Comprehensive testing guide
|
|
214
|
+
- 🎯 [Examples](examples/) - Sample implementations
|
|
215
|
+
|
|
216
|
+
## Support
|
|
217
|
+
|
|
218
|
+
- **Documentation**: https://docs.mixpeek.com
|
|
219
|
+
- **Email**: support@mixpeek.com
|
|
220
|
+
- **GitHub Issues**: https://github.com/mixpeek/prebid-contextual-adapter/issues
|
|
221
|
+
|
|
222
|
+
## What's Next?
|
|
223
|
+
|
|
224
|
+
The adapter is now set up and ready to use! Here's what happens:
|
|
225
|
+
|
|
226
|
+
1. **Page loads** with Prebid and Mixpeek adapter
|
|
227
|
+
2. **Content is extracted** from the page (text, images, video)
|
|
228
|
+
3. **Mixpeek API classifies** the content into IAB categories
|
|
229
|
+
4. **Targeting keys** are injected into bid requests
|
|
230
|
+
5. **Bidders receive** enriched requests with contextual signals
|
|
231
|
+
6. **Ads are served** based on content relevance
|
|
232
|
+
|
|
233
|
+
All of this happens in <250ms without blocking your ad auction! 🚀
|
|
234
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
# Mixpeek Contextual Adapter for Prebid.js
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@mixpeek/prebid)
|
|
4
|
+
[](https://www.npmjs.com/package/@mixpeek/prebid)
|
|
5
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
6
|
+
[](https://www.npmjs.com/package/@mixpeek/prebid)
|
|
7
|
+
[](https://bundlephobia.com/package/@mixpeek/prebid)
|
|
8
|
+
[](https://www.npmjs.com/package/@mixpeek/prebid)
|
|
9
|
+
[](https://github.com/mixpeek/prebid)
|
|
10
|
+
[](https://github.com/mixpeek/prebid/pulls)
|
|
11
|
+
|
|
12
|
+
## 🎯 Overview
|
|
13
|
+
|
|
14
|
+
The Mixpeek Contextual Adapter enables publishers and SSPs using **Prebid.js** to enrich bid requests with real-time contextual data powered by Mixpeek's multimodal AI engine. This adapter provides:
|
|
15
|
+
|
|
16
|
+
- **Privacy-First Targeting**: No cookies, just content-based context
|
|
17
|
+
- **Multimodal Analysis**: Text, images, video, and audio processing
|
|
18
|
+
- **IAB Taxonomy**: Automatic classification into IAB content categories
|
|
19
|
+
- **Brand Safety**: Real-time brand safety scoring
|
|
20
|
+
- **Ad Adjacency Awareness**: Tracks previous ad to avoid repetition and improve user experience
|
|
21
|
+
- **Sub-100ms Performance**: Optimized for header bidding speed requirements
|
|
22
|
+
- **Graceful Fallbacks**: Never blocks the auction
|
|
23
|
+
|
|
24
|
+
## 🚀 Quick Start
|
|
25
|
+
|
|
26
|
+
### Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install @mixpeek/prebid
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Basic Setup
|
|
33
|
+
|
|
34
|
+
```javascript
|
|
35
|
+
// 1. Include the Mixpeek RTD module
|
|
36
|
+
import '@mixpeek/prebid'
|
|
37
|
+
|
|
38
|
+
// 2. Configure Mixpeek as an RTD provider
|
|
39
|
+
pbjs.setConfig({
|
|
40
|
+
realTimeData: {
|
|
41
|
+
auctionDelay: 250, // Max time to wait for contextual data (ms)
|
|
42
|
+
dataProviders: [{
|
|
43
|
+
name: 'mixpeek',
|
|
44
|
+
waitForIt: true, // Wait for Mixpeek before starting auction
|
|
45
|
+
params: {
|
|
46
|
+
apiKey: 'YOUR_MIXPEEK_API_KEY',
|
|
47
|
+
collectionId: 'your-collection-id',
|
|
48
|
+
|
|
49
|
+
// Use development server (temporary)
|
|
50
|
+
endpoint: 'https://server-xb24.onrender.com',
|
|
51
|
+
// Or production: endpoint: 'https://api.mixpeek.com',
|
|
52
|
+
|
|
53
|
+
namespace: 'your-namespace', // optional
|
|
54
|
+
featureExtractors: ['taxonomy', 'brand-safety'],
|
|
55
|
+
mode: 'page', // 'page', 'video', or 'auto'
|
|
56
|
+
timeout: 5000, // ms - higher for dev server
|
|
57
|
+
cacheTTL: 300 // seconds
|
|
58
|
+
}
|
|
59
|
+
}]
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
// 3. The RTD module automatically enriches bid requests!
|
|
64
|
+
pbjs.requestBids({
|
|
65
|
+
adUnits: [...],
|
|
66
|
+
bidsBackHandler: function(bids) {
|
|
67
|
+
// Bids now include Mixpeek contextual data in ortb2
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## 📋 Prerequisites
|
|
73
|
+
|
|
74
|
+
1. **Mixpeek Account**: Sign up at [mixpeek.com](https://mixpeek.com/start)
|
|
75
|
+
2. **API Key**: Generate an API key in your Mixpeek dashboard
|
|
76
|
+
3. **Collection**: Create a collection with feature extractors configured
|
|
77
|
+
4. **Prebid.js**: Version 6.0.0 or higher
|
|
78
|
+
|
|
79
|
+
## 🔧 Configuration Options
|
|
80
|
+
|
|
81
|
+
### RTD Configuration
|
|
82
|
+
|
|
83
|
+
| Option | Type | Required | Default | Description |
|
|
84
|
+
|--------|------|----------|---------|-------------|
|
|
85
|
+
| `realTimeData.auctionDelay` | number | ❌ | 250 | Max time to wait for all RTD providers (ms) |
|
|
86
|
+
| `realTimeData.dataProviders[].name` | string | ✅ | - | Must be `'mixpeek'` |
|
|
87
|
+
| `realTimeData.dataProviders[].waitForIt` | boolean | ❌ | false | Wait for Mixpeek before starting auction |
|
|
88
|
+
|
|
89
|
+
### Mixpeek Parameters
|
|
90
|
+
|
|
91
|
+
| Option | Type | Required | Default | Description |
|
|
92
|
+
|--------|------|----------|---------|-------------|
|
|
93
|
+
| `params.apiKey` | string | ✅ | - | Your Mixpeek API key |
|
|
94
|
+
| `params.collectionId` | string | ✅ | - | Mixpeek collection ID for document processing |
|
|
95
|
+
| `params.endpoint` | string | ❌ | `https://server-xb24.onrender.com` | Mixpeek API endpoint (dev server default) |
|
|
96
|
+
| `params.namespace` | string | ❌ | - | Optional namespace for data isolation |
|
|
97
|
+
| `params.featureExtractors` | array | ❌ | `['taxonomy']` | Feature extractors to use (taxonomy, brand-safety, etc.) |
|
|
98
|
+
| `params.mode` | string | ❌ | `auto` | Content mode: `page`, `video`, `image`, or `auto` |
|
|
99
|
+
| `params.timeout` | number | ❌ | 250 | API request timeout in milliseconds |
|
|
100
|
+
| `params.cacheTTL` | number | ❌ | 300 | Cache TTL in seconds |
|
|
101
|
+
| `params.enableCache` | boolean | ❌ | `true` | Enable local caching |
|
|
102
|
+
| `params.debug` | boolean | ❌ | `false` | Enable debug logging |
|
|
103
|
+
| `params.batchSize` | number | ❌ | 1 | Number of concurrent requests |
|
|
104
|
+
| `params.retryAttempts` | number | ❌ | 2 | Number of retry attempts on failure |
|
|
105
|
+
|
|
106
|
+
## 📊 Output: OpenRTB 2.6 Data Structure
|
|
107
|
+
|
|
108
|
+
The RTD module injects contextual data into your bid requests using the OpenRTB 2.6 standard:
|
|
109
|
+
|
|
110
|
+
### Site-Level Data (`ortb2.site.content`)
|
|
111
|
+
```javascript
|
|
112
|
+
{
|
|
113
|
+
"ortb2": {
|
|
114
|
+
"site": {
|
|
115
|
+
"content": {
|
|
116
|
+
"cat": ["IAB19-11"], // IAB Content Categories
|
|
117
|
+
"cattax": 6, // IAB Content Taxonomy v3.0
|
|
118
|
+
"genre": "Technology - AI", // Human-readable category
|
|
119
|
+
"keywords": "ai,technology,ml", // Extracted keywords
|
|
120
|
+
"language": "en", // Content language
|
|
121
|
+
"title": "Article Title", // Page title
|
|
122
|
+
"url": "https://example.com", // Page URL
|
|
123
|
+
"ext": {
|
|
124
|
+
"data": {
|
|
125
|
+
"mixpeek": {
|
|
126
|
+
"score": 0.94, // Confidence score
|
|
127
|
+
"brandSafety": 0.98, // Brand safety score
|
|
128
|
+
"sentiment": "positive", // Content sentiment
|
|
129
|
+
"embeddingId": "emb_abc123" // Embedding ID
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Impression-Level Data (`ortb2Imp.ext.data`)
|
|
140
|
+
```javascript
|
|
141
|
+
{
|
|
142
|
+
// Current page context
|
|
143
|
+
"hb_mixpeek_taxonomy": "IAB19-11", // Primary IAB taxonomy code
|
|
144
|
+
"hb_mixpeek_category": "Technology > AI", // Human-readable category
|
|
145
|
+
"hb_mixpeek_node": "node_tech_ai", // Taxonomy node ID
|
|
146
|
+
"hb_mixpeek_path": "tech/ai/ml", // Hierarchical path
|
|
147
|
+
"hb_mixpeek_score": "0.94", // Confidence score
|
|
148
|
+
"hb_mixpeek_safety": "0.98", // Brand safety score
|
|
149
|
+
"hb_mixpeek_keywords": "AI,ML,tech", // Extracted keywords
|
|
150
|
+
"hb_mixpeek_embed": "emb_abc123", // Embedding ID for retrieval
|
|
151
|
+
|
|
152
|
+
// Previous ad context (adjacency awareness)
|
|
153
|
+
"hb_mixpeek_prev_creative": "12345", // Last creative ID shown
|
|
154
|
+
"hb_mixpeek_prev_bidder": "appnexus", // Last bidder that won
|
|
155
|
+
"hb_mixpeek_prev_adunit": "sidebar-1", // Last ad unit code
|
|
156
|
+
"hb_mixpeek_prev_cat": "IAB18-1,IAB12-3" // Last ad categories
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## 🎥 Usage Examples
|
|
161
|
+
|
|
162
|
+
### Page Context (Articles, Blogs)
|
|
163
|
+
|
|
164
|
+
```javascript
|
|
165
|
+
pbjs.setConfig({
|
|
166
|
+
realTimeData: {
|
|
167
|
+
auctionDelay: 250,
|
|
168
|
+
dataProviders: [{
|
|
169
|
+
name: 'mixpeek',
|
|
170
|
+
waitForIt: true,
|
|
171
|
+
params: {
|
|
172
|
+
apiKey: 'sk_your_api_key',
|
|
173
|
+
collectionId: 'col_articles',
|
|
174
|
+
mode: 'page',
|
|
175
|
+
featureExtractors: ['taxonomy', 'brand-safety', 'keywords']
|
|
176
|
+
}
|
|
177
|
+
}]
|
|
178
|
+
}
|
|
179
|
+
})
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Video Context (Pre-roll, Mid-roll)
|
|
183
|
+
|
|
184
|
+
```javascript
|
|
185
|
+
pbjs.setConfig({
|
|
186
|
+
realTimeData: {
|
|
187
|
+
auctionDelay: 300, // Longer delay for video processing
|
|
188
|
+
dataProviders: [{
|
|
189
|
+
name: 'mixpeek',
|
|
190
|
+
waitForIt: true,
|
|
191
|
+
params: {
|
|
192
|
+
apiKey: 'sk_your_api_key',
|
|
193
|
+
collectionId: 'col_videos',
|
|
194
|
+
mode: 'video',
|
|
195
|
+
videoSelector: '#main-video', // CSS selector for video element
|
|
196
|
+
featureExtractors: ['taxonomy', 'scene-detection']
|
|
197
|
+
}
|
|
198
|
+
}]
|
|
199
|
+
}
|
|
200
|
+
})
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Multi-Content Auto-Detection
|
|
204
|
+
|
|
205
|
+
```javascript
|
|
206
|
+
pbjs.setConfig({
|
|
207
|
+
realTimeData: {
|
|
208
|
+
auctionDelay: 250,
|
|
209
|
+
dataProviders: [{
|
|
210
|
+
name: 'mixpeek',
|
|
211
|
+
waitForIt: true,
|
|
212
|
+
params: {
|
|
213
|
+
apiKey: 'sk_your_api_key',
|
|
214
|
+
collectionId: 'col_mixed',
|
|
215
|
+
mode: 'auto', // Automatically detects page, video, or image content
|
|
216
|
+
featureExtractors: ['taxonomy', 'brand-safety', 'clustering']
|
|
217
|
+
}
|
|
218
|
+
}]
|
|
219
|
+
}
|
|
220
|
+
})
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## 🏗️ How It Works
|
|
224
|
+
|
|
225
|
+
```mermaid
|
|
226
|
+
sequenceDiagram
|
|
227
|
+
participant Publisher
|
|
228
|
+
participant Prebid
|
|
229
|
+
participant MixpeekAdapter
|
|
230
|
+
participant MixpeekAPI
|
|
231
|
+
participant SSP
|
|
232
|
+
|
|
233
|
+
Publisher->>Prebid: Request Bids
|
|
234
|
+
Prebid->>MixpeekAdapter: beforeRequestBids event
|
|
235
|
+
MixpeekAdapter->>MixpeekAdapter: Extract page/video content
|
|
236
|
+
MixpeekAdapter->>MixpeekAdapter: Check cache
|
|
237
|
+
alt Cache Miss
|
|
238
|
+
MixpeekAdapter->>MixpeekAPI: POST /collections/{id}/documents
|
|
239
|
+
MixpeekAPI->>MixpeekAPI: Process with feature extractors
|
|
240
|
+
MixpeekAPI-->>MixpeekAdapter: Return enrichments
|
|
241
|
+
MixpeekAdapter->>MixpeekAdapter: Cache result
|
|
242
|
+
end
|
|
243
|
+
MixpeekAdapter->>Prebid: Inject contextual key-values
|
|
244
|
+
Prebid->>SSP: Send enriched bid request
|
|
245
|
+
SSP-->>Prebid: Return bids
|
|
246
|
+
Prebid-->>Publisher: Render ad
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## 🧪 Testing
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
# Run all tests
|
|
253
|
+
npm test
|
|
254
|
+
|
|
255
|
+
# Run with coverage
|
|
256
|
+
npm run test:coverage
|
|
257
|
+
|
|
258
|
+
# Watch mode
|
|
259
|
+
npm run test:watch
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## 📖 Advanced Configuration
|
|
263
|
+
|
|
264
|
+
### Custom Feature Extractors
|
|
265
|
+
|
|
266
|
+
```javascript
|
|
267
|
+
pbjs.setConfig({
|
|
268
|
+
realTimeData: {
|
|
269
|
+
auctionDelay: 250,
|
|
270
|
+
dataProviders: [{
|
|
271
|
+
name: 'mixpeek',
|
|
272
|
+
waitForIt: true,
|
|
273
|
+
params: {
|
|
274
|
+
apiKey: 'sk_your_api_key',
|
|
275
|
+
collectionId: 'col_custom',
|
|
276
|
+
customExtractors: [
|
|
277
|
+
{
|
|
278
|
+
feature_extractor_id: 'sentiment-analyzer',
|
|
279
|
+
payload: {
|
|
280
|
+
model: 'sentiment-v2',
|
|
281
|
+
threshold: 0.7
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
]
|
|
285
|
+
}
|
|
286
|
+
}]
|
|
287
|
+
}
|
|
288
|
+
})
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Conditional Loading
|
|
292
|
+
|
|
293
|
+
```javascript
|
|
294
|
+
// Only enrich on specific pages
|
|
295
|
+
if (window.location.pathname.startsWith('/articles/')) {
|
|
296
|
+
pbjs.setConfig({
|
|
297
|
+
realTimeData: {
|
|
298
|
+
auctionDelay: 250,
|
|
299
|
+
dataProviders: [{
|
|
300
|
+
name: 'mixpeek',
|
|
301
|
+
waitForIt: true,
|
|
302
|
+
params: {
|
|
303
|
+
apiKey: 'sk_your_api_key',
|
|
304
|
+
collectionId: 'col_articles',
|
|
305
|
+
mode: 'page'
|
|
306
|
+
}
|
|
307
|
+
}]
|
|
308
|
+
}
|
|
309
|
+
})
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Event Callbacks
|
|
314
|
+
|
|
315
|
+
```javascript
|
|
316
|
+
pbjs.onEvent('mixpeekContextReady', function(context) {
|
|
317
|
+
console.log('Mixpeek context loaded:', context)
|
|
318
|
+
// Custom analytics or modifications
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
pbjs.onEvent('mixpeekContextError', function(error) {
|
|
322
|
+
console.error('Mixpeek context error:', error)
|
|
323
|
+
// Custom error handling
|
|
324
|
+
})
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
## 🔄 Previous Ad Tracking (Adjacency Awareness)
|
|
328
|
+
|
|
329
|
+
The adapter automatically tracks the most recently served ad to enable adjacency-aware targeting. This helps:
|
|
330
|
+
|
|
331
|
+
- **Avoid Ad Repetition**: Prevent showing the same creative or category repeatedly
|
|
332
|
+
- **Frequency Capping**: Build frequency cap rules based on previous impressions
|
|
333
|
+
- **Competitive Separation**: Avoid showing competing brands consecutively
|
|
334
|
+
- **Enhanced User Experience**: Improve ad diversity and relevance
|
|
335
|
+
|
|
336
|
+
### How It Works
|
|
337
|
+
|
|
338
|
+
1. **Automatic Tracking**: On every `bidResponse` event, the adapter stores minimal information about the winning ad
|
|
339
|
+
2. **Lightweight Storage**: Data is stored in memory + localStorage (privacy-safe, no PII)
|
|
340
|
+
3. **Targeting Keys**: Previous ad data is automatically injected into subsequent bid requests
|
|
341
|
+
|
|
342
|
+
### Data Tracked
|
|
343
|
+
|
|
344
|
+
| Field | Description | Example |
|
|
345
|
+
|-------|-------------|---------|
|
|
346
|
+
| `creativeId` | Winning creative ID | `"12345"` |
|
|
347
|
+
| `bidder` | Winning bidder code | `"appnexus"` |
|
|
348
|
+
| `adUnitCode` | Ad unit that served the ad | `"sidebar-1"` |
|
|
349
|
+
| `categories` | IAB categories of the ad | `["IAB18-1", "IAB12-3"]` |
|
|
350
|
+
| `timestamp` | When the ad was served | `1697123456789` |
|
|
351
|
+
|
|
352
|
+
### Targeting Keys Injected
|
|
353
|
+
|
|
354
|
+
The following keys are automatically added to `ortb2Imp.ext.data`:
|
|
355
|
+
|
|
356
|
+
- `hb_mixpeek_prev_creative` - Last creative ID
|
|
357
|
+
- `hb_mixpeek_prev_bidder` - Last winning bidder
|
|
358
|
+
- `hb_mixpeek_prev_adunit` - Last ad unit code
|
|
359
|
+
- `hb_mixpeek_prev_cat` - Last ad categories (comma-separated)
|
|
360
|
+
|
|
361
|
+
### SSP/DSP Usage
|
|
362
|
+
|
|
363
|
+
SSPs and DSPs can use these keys for advanced targeting rules:
|
|
364
|
+
|
|
365
|
+
```javascript
|
|
366
|
+
// Example: Avoid showing the same creative twice in a row
|
|
367
|
+
if (bidRequest.ortb2Imp.ext.data.hb_mixpeek_prev_creative === currentCreative.id) {
|
|
368
|
+
// Skip this creative or reduce bid
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Example: Competitive separation
|
|
372
|
+
const prevCategories = bidRequest.ortb2Imp.ext.data.hb_mixpeek_prev_cat?.split(',') || []
|
|
373
|
+
if (prevCategories.includes('IAB18-1') && currentAd.category === 'IAB18-1') {
|
|
374
|
+
// Don't show competing fashion ads back-to-back
|
|
375
|
+
}
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### Privacy & Storage
|
|
379
|
+
|
|
380
|
+
- **No User Tracking**: Only ad metadata is stored, no user identifiers or behavior
|
|
381
|
+
- **Session-Scoped**: Data persists across page views within a session
|
|
382
|
+
- **Local Storage**: Falls back to memory-only if localStorage is unavailable
|
|
383
|
+
- **Minimal Data**: Only essential fields are stored (< 200 bytes)
|
|
384
|
+
- **GDPR/CCPA Compliant**: No consent required as it doesn't track users
|
|
385
|
+
|
|
386
|
+
### Programmatic Control
|
|
387
|
+
|
|
388
|
+
You can access the previous ad tracker directly if needed:
|
|
389
|
+
|
|
390
|
+
```javascript
|
|
391
|
+
import previousAdTracker from '@mixpeek/prebid/utils/previousAdTracker'
|
|
392
|
+
|
|
393
|
+
// Get last ad info
|
|
394
|
+
const lastAd = previousAdTracker.getLast()
|
|
395
|
+
console.log('Last creative:', lastAd?.creativeId)
|
|
396
|
+
|
|
397
|
+
// Clear history (e.g., on user logout or page type change)
|
|
398
|
+
previousAdTracker.clear()
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
## 🔒 Security & Privacy
|
|
402
|
+
|
|
403
|
+
- **No PII**: The adapter never sends user identifiers or cookies
|
|
404
|
+
- **Content-Only**: Only page/video content is analyzed
|
|
405
|
+
- **HTTPS**: All API calls use TLS encryption
|
|
406
|
+
- **API Key Safety**: Store API keys securely (environment variables, server-side rendering)
|
|
407
|
+
- **GDPR/CCPA Compliant**: Contextual targeting doesn't require user consent
|
|
408
|
+
|
|
409
|
+
## 📚 Documentation
|
|
410
|
+
|
|
411
|
+
### User Guides
|
|
412
|
+
- [Quick Start](QUICKSTART.md) - Get running in 5 minutes
|
|
413
|
+
- [Integration Guide](docs/integration-guide.md) - Step-by-step integration
|
|
414
|
+
- [API Reference](docs/api-reference.md) - Complete API documentation
|
|
415
|
+
- [Testing Guide](TESTING.md) - How to test the adapter
|
|
416
|
+
- [Endpoint Configuration](ENDPOINTS.md) - Configure API endpoints
|
|
417
|
+
- [Health Check](docs/health-check.md) - Health check configuration
|
|
418
|
+
|
|
419
|
+
### Developer Resources
|
|
420
|
+
- [Mixpeek API Docs](https://docs.mixpeek.com) - Platform documentation
|
|
421
|
+
- [Internal Planning](tasks/) - Gap analysis & implementation plans (internal)
|
|
422
|
+
|
|
423
|
+
## 🤝 Support
|
|
424
|
+
|
|
425
|
+
- **Email**: support@mixpeek.com
|
|
426
|
+
- **GitHub Issues**: [Create an issue](https://github.com/mixpeek/prebid/issues)
|
|
427
|
+
- **Documentation**: [docs.mixpeek.com](https://docs.mixpeek.com)
|
|
428
|
+
- **Slack Community**: [Join our Slack](https://mixpeek.com/slack)
|
|
429
|
+
|
|
430
|
+
## 📄 License
|
|
431
|
+
|
|
432
|
+
Apache 2.0 - see [LICENSE](LICENSE) file for details.
|
|
433
|
+
|
|
434
|
+
## 🙏 Credits
|
|
435
|
+
|
|
436
|
+
Built with ❤️ by [Mixpeek](https://mixpeek.com)
|
|
437
|
+
|
|
438
|
+
Integrates with [Prebid.js](https://prebid.org) - an open-source header bidding solution
|
|
439
|
+
|