Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ccac679
Paywalls RTD Adapter: implement paywallsRtdProvider module
paywalls-mike Mar 1, 2026
3ba6f6d
Paywalls RTD Adapter: add e2e integration test page and mock VAI script
paywalls-mike Mar 1, 2026
a4f29c6
Paywalls RTD Adapter: add module documentation (paywallsRtdProvider.md)
paywalls-mike Mar 1, 2026
c23f988
Paywalls RTD Adapter: update hosting modes in docs
paywalls-mike Mar 1, 2026
aa5472b
Paywalls RTD Adapter: fix activity controls docs
paywalls-mike Mar 1, 2026
bf397fd
Paywalls Analytics Adapter: emit VAI vat/act
paywalls-mike Mar 1, 2026
d6a8573
Paywalls Modules: fix doc links and improve RTD provider docs
paywalls-mike Mar 1, 2026
c2e15ae
Paywalls Modules: fix VAI URL to paywalls.net/docs (not docs.paywalls…
paywalls-mike Mar 1, 2026
d145be8
Paywalls Modules: fix bugs from automated review feedback
paywalls-mike Mar 1, 2026
b076394
Paywalls RTD Provider: VAI refactor - field renames, 3-scope ORTB2, pvtk
paywalls-mike Mar 3, 2026
aa286d0
Paywalls RTD Provider: update mock-vai.js to match VAI refactor field…
paywalls-mike Mar 3, 2026
93da8e7
Paywalls RTD Provider: never shorten late hook grace window on script…
paywalls-mike Mar 3, 2026
e6bce50
Merge branch 'master' into agent/paywalls-rtd-provider
paywalls-mike Mar 4, 2026
adf48b9
Paywalls RTD Provider: remove timer-based hook cleanup, let hook self…
paywalls-mike Mar 4, 2026
e30638a
Paywalls RTD Provider: remove hook chaining to prevent closure accumu…
paywalls-mike Mar 4, 2026
2a90533
Paywalls RTD Provider: update integration example for VAI schema changes
paywalls-mike Mar 5, 2026
28bc3bc
Merge branch 'master' into agent/paywalls-rtd-provider
paywalls-mike Mar 9, 2026
b7046c7
Paywalls Adapters: fix object-curly-spacing lint errors
paywalls-mike Mar 10, 2026
e17a323
Paywalls RTD Provider: rename params.waitForIt to params.timeout; fix…
paywalls-mike Mar 10, 2026
9e492d2
Merge branch 'master' into agent/paywalls-rtd-provider
patmmccann Mar 11, 2026
3c24357
Paywalls RTD Adapter: place VAI data at ext.data per Prebid FPD conve…
paywalls-mike Mar 11, 2026
fcb8d2b
Paywalls RTD/Analytics Adapters: remove vai.js script injection
paywalls-mike Mar 13, 2026
d4a149e
Paywalls RTD Provider: fix ORTB2 docs to use ext.data for site/user V…
paywalls-mike Mar 13, 2026
104bef3
Paywalls: remove stale loadExternalScript approvals
paywalls-mike Mar 18, 2026
b4c96dc
Paywalls RTD Provider: register RTD submodule
paywalls-mike Mar 18, 2026
b84f0e2
Merge branch 'master' into agent/paywalls-rtd-provider
patmmccann Mar 20, 2026
2176f3c
Merge branch 'master' into agent/paywalls-rtd-provider
patmmccann Apr 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions integrationExamples/gpt/mock-vai.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* Mock VAI script for integration testing.
*
* Simulates what the real vai.js (from /pw/vai.js) would do:
* 1. Classify the actor (vat/act)
* 2. Build a signed assertion
* 3. Set window.__PW_VAI__ with the full payload
* 4. Call window.__PW_VAI_HOOK__ if registered
* 5. Cache in localStorage
*
* All JWT/JWS values are dummy strings — this is for structural
* verification only, not cryptographic validation.
*/
(function () {
'use strict';

var payload = {
// Actor classification (user.ext.vai)
vat: 'HUMAN',
act: 'ACT-1',
// Domain provenance (site.ext.vai)
iss: 'paywalls.net',
dom: location.hostname,
// Signed assertion (user.ext.vai)
jws: 'eyJhbGciOiJFZERTQSIsImtpZCI6InRlc3Qta2V5LTAwMSJ9.eyJpc3MiOiJwYXl3YWxscy5uZXQifQ.mock-signature',
// Meter/session token (user.ext.vai)
mstk: 'mock-mstk-token-001',
// Per-view token (imp[].ext.vai)
pvtk: 'mock-pvtk-token-001',
// Expiry — 1 hour from now
exp: Math.floor(Date.now() / 1000) + 3600,
};

// Set window global
window.__PW_VAI__ = payload;

// Notify hook if registered
if (typeof window.__PW_VAI_HOOK__ === 'function') {
window.__PW_VAI_HOOK__(payload);
}

// Cache in localStorage
try {
localStorage.setItem('__pw_vai__', JSON.stringify(payload));
} catch (e) {
// Ignore — storage may be unavailable
}

console.log('[mock-vai.js] VAI payload set:', payload);
})();
177 changes: 177 additions & 0 deletions integrationExamples/gpt/paywallsAnalyticsAdapter_example.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
<!--
Paywalls Analytics Adapter — Integration Test Page

Verifies that the analytics adapter emits vai_vat and vai_act
on each auction. Uses a single appnexus test placement (nobid is fine —
the adapter fires on AUCTION_END regardless of bid outcome).

Publishers must load vai.js before Prebid.js initializes.
The analytics adapter reads window.__PW_VAI__ directly.

Serve with: npx gulp serve-fast --modules=paywallsAnalyticsAdapter,appnexusBidAdapter
Then open: http://localhost:9999/integrationExamples/gpt/paywallsAnalyticsAdapter_example.html

Modes:
?degrade — skip vai.js loading to test graceful degradation
?real — use real VAI from production (requires network)
(default) — use mock-vai.js for deterministic testing
-->
<html>
<head>
<title>Paywalls Analytics Adapter — E2E Test</title>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 700px; margin: 2em auto; }
h2 { color: #1a1a2e; }
.status { padding: 0.5em 1em; margin: 0.5em 0; border-left: 4px solid #ccc; background: #f9f9f9; font-family: monospace; white-space: pre-wrap; }
.pass { border-left-color: #27ae60; background: #eafaf1; }
.fail { border-left-color: #e74c3c; background: #fdedec; }
.wait { border-left-color: #f39c12; background: #fef9e7; }
</style>

<script>
var degradeMode = location.search.indexOf('degrade') !== -1;
var realMode = location.search.indexOf('real') !== -1;
</script>

<!-- Load vai.js BEFORE Prebid — the analytics adapter reads window.__PW_VAI__ -->
<script id="vai-script"></script>
<script>
(function() {
var script = document.getElementById('vai-script');
if (degradeMode) {
script.remove();
} else if (realMode) {
script.src = 'https://paywalls.net/pw/vai.js';
} else {
script.src = '/integrationExamples/gpt/mock-vai.js';
}
})();
</script>

<script>
var pbjs = pbjs || {};
pbjs.que = pbjs.que || [];
</script>
<!-- Load Prebid dev build -->
<script src="../../build/dev/prebid.js" async></script>

<script>
if (degradeMode) {
delete window.__PW_VAI__;
document.title = 'Paywalls Analytics — Degradation Test';
}
if (realMode) {
document.title = 'Paywalls Analytics — Real VAI Test';
}

var collectedMetrics = null;

function setStatus(id, pass, msg) {
var el = document.getElementById(id);
if (el) {
el.className = 'status ' + (pass ? 'pass' : 'fail');
el.textContent = (pass ? '✓ ' : '✗ ') + msg;
}
}

function runAssertions() {
if (!collectedMetrics) {
setStatus('test-callback', false, 'Analytics callback was NOT called');
setStatus('summary', false, 'FAIL — no callback');
return;
}
setStatus('test-callback', true, 'Analytics callback fired');

var m = collectedMetrics;

// VAI classification
if (degradeMode) {
if (m.vai_vat === 'UNKNOWN' && m.vai_act === 'UNKNOWN') {
setStatus('test-vat', true, 'vai_vat=UNKNOWN (expected — degrade mode)');
setStatus('test-act', true, 'vai_act=UNKNOWN (expected — degrade mode)');
} else {
setStatus('test-vat', false, 'Expected UNKNOWN, got vai_vat=' + m.vai_vat);
setStatus('test-act', false, 'Expected UNKNOWN, got vai_act=' + m.vai_act);
}
} else {
var vatOk = m.vai_vat && m.vai_vat !== 'UNKNOWN';
var actOk = m.vai_act && m.vai_act !== 'UNKNOWN';
setStatus('test-vat', vatOk, 'vai_vat=' + m.vai_vat);
setStatus('test-act', actOk, 'vai_act=' + m.vai_act);
}

// Only vat and act should be present
var keys = Object.keys(m);
var onlyTwo = keys.length === 2 && m.vai_vat !== undefined && m.vai_act !== undefined;
setStatus('test-keys', onlyTwo, 'Keys: ' + keys.join(', ') + (onlyTwo ? '' : ' (expected only vai_vat, vai_act)'));

// Summary
var allPass = [
collectedMetrics != null,
degradeMode ? (m.vai_vat === 'UNKNOWN') : (m.vai_vat && m.vai_vat !== 'UNKNOWN'),
degradeMode ? (m.vai_act === 'UNKNOWN') : (m.vai_act && m.vai_act !== 'UNKNOWN'),
onlyTwo
].every(Boolean);
setStatus('summary', allPass, allPass ? 'All tests passed' : 'Some tests failed');

console.log('[e2e-analytics] Metrics:', JSON.stringify(m));
}

// ─── Prebid config ──────────────────────────────────────────
pbjs.que.push(function () {
pbjs.setConfig({
debug: true
});

pbjs.enableAnalytics([{
provider: 'paywalls',
options: {
output: 'callback',
callback: function (metrics) {
console.log('[e2e-analytics] callback:', metrics);
collectedMetrics = metrics;
setTimeout(runAssertions, 100);
},
samplingRate: 1.0
}
}]);

pbjs.addAdUnits([{
code: 'test-div-1',
mediaTypes: { banner: { sizes: [[300, 250]] } },
bids: [{ bidder: 'appnexus', params: { placementId: 13144370 } }]
}]);

pbjs.requestBids({
bidsBackHandler: function () {
console.log('[e2e-analytics] bidsBackHandler fired');
setTimeout(function () {
if (!collectedMetrics) {
setStatus('test-callback', false, 'Analytics callback never fired (timeout)');
setStatus('summary', false, 'Timed out');
}
}, 5000);
}
});
});
</script>
</head>

<body>
<h2>Paywalls Analytics Adapter — Integration Test</h2>
<p>Verifies <code>vai_vat</code> and <code>vai_act</code> are emitted on each auction.</p>

<div id="test-callback" class="status wait">⏳ Waiting for auction...</div>
<div id="test-vat" class="status wait">⏳ vai_vat</div>
<div id="test-act" class="status wait">⏳ vai_act</div>
<div id="test-keys" class="status wait">⏳ Only vat/act keys</div>
<div id="summary" class="status wait">⏳ Running...</div>

<div id="test-div-1" style="min-width:300px; min-height:100px; border:2px dashed #ccc; display:flex; align-items:center; justify-content:center; color:#999; margin:1em 0;">(ad slot)</div>

<p style="color:#666; font-size:0.9em;">
<code>?degrade</code> — test with VAI unavailable &nbsp;|&nbsp;
<code>?real</code> — use real VAI from production
</p>
</body>
</html>
Loading
Loading