-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
dynamicAdBoostModule: New module (#10377)
- Loading branch information
1 parent
5bea607
commit 56292c4
Showing
3 changed files
with
231 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
/** | ||
* The {@link module:modules/realTimeData} module is required | ||
* @module modules/dynamicAdBoost | ||
* @requires module:modules/realTimeData | ||
*/ | ||
|
||
import { submodule } from '../src/hook.js' | ||
import { loadExternalScript } from '../src/adloader.js'; | ||
import { getGlobal } from '../src/prebidGlobal.js'; | ||
import { deepAccess, deepSetValue, isEmptyStr } from '../src/utils.js'; | ||
|
||
const MODULE_NAME = 'dynamicAdBoost'; | ||
const SCRIPT_URL = 'https://adxbid.info'; | ||
const CLIENT_SUPPORTS_IO = window.IntersectionObserver && window.IntersectionObserverEntry && window.IntersectionObserverEntry.prototype && | ||
'intersectionRatio' in window.IntersectionObserverEntry.prototype; | ||
// Options for the Intersection Observer | ||
const dabOptions = { | ||
threshold: 0.5 // Trigger callback when 50% of the element is visible | ||
}; | ||
let observer; | ||
let dabStartDate; | ||
let dabStartTime; | ||
|
||
// Array of div IDs to track | ||
let dynamicAdBoostAdUnits = {}; | ||
|
||
function init(config, userConsent) { | ||
dabStartDate = new Date(); | ||
dabStartTime = dabStartDate.getTime(); | ||
if (!CLIENT_SUPPORTS_IO) { | ||
return false; | ||
} | ||
// Create an Intersection Observer instance | ||
observer = new IntersectionObserver(dabHandleIntersection, dabOptions); | ||
if (config.params.keyId) { | ||
let keyId = config.params.keyId; | ||
if (keyId && !isEmptyStr(keyId)) { | ||
let dabDivIdsToTrack = config.params.adUnits; | ||
let dabInterval = setInterval(function() { | ||
// Observe each div by its ID | ||
dabDivIdsToTrack.forEach(divId => { | ||
let div = document.getElementById(divId); | ||
if (div) { | ||
observer.observe(div); | ||
} | ||
}); | ||
|
||
let dabDateNow = new Date(); | ||
let dabTimeNow = dabDateNow.getTime(); | ||
let dabElapsedSeconds = Math.floor((dabTimeNow - dabStartTime) / 1000); | ||
let elapsedThreshold = 30; | ||
if (config.params.threshold) { | ||
elapsedThreshold = config.params.threshold; | ||
} | ||
if (dabElapsedSeconds >= elapsedThreshold) { | ||
clearInterval(dabInterval); // Stop | ||
loadLmScript(keyId); | ||
} | ||
}, 1000); | ||
|
||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
function loadLmScript(keyId) { | ||
let viewableAdUnits = Object.keys(dynamicAdBoostAdUnits); | ||
let viewableAdUnitsCSV = viewableAdUnits.join(','); | ||
const scriptUrl = `${SCRIPT_URL}/${keyId}.js?viewableAdUnits=${viewableAdUnitsCSV}`; | ||
loadExternalScript(scriptUrl, MODULE_NAME); | ||
observer.disconnect(); | ||
} | ||
|
||
function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { | ||
const reqAdUnits = reqBidsConfigObj.adUnits || getGlobal().adUnits; | ||
|
||
if (Array.isArray(reqAdUnits)) { | ||
reqAdUnits.forEach(adunit => { | ||
let gptCode = deepAccess(adunit, 'code'); | ||
if (dynamicAdBoostAdUnits.hasOwnProperty(gptCode)) { | ||
// AdUnits has reached target viewablity at some point | ||
deepSetValue(adunit, `ortb2Imp.ext.data.${MODULE_NAME}.${gptCode}`, dynamicAdBoostAdUnits[gptCode]); | ||
} | ||
}); | ||
} | ||
callback(); | ||
} | ||
|
||
let markViewed = (entry, observer) => { | ||
return () => { | ||
observer.unobserve(entry.target); | ||
} | ||
} | ||
|
||
// Callback function when an observed element becomes visible | ||
function dabHandleIntersection(entries) { | ||
entries.forEach(entry => { | ||
if (entry.isIntersecting && entry.intersectionRatio > 0.5) { | ||
dynamicAdBoostAdUnits[entry.target.id] = entry.intersectionRatio; | ||
markViewed(entry, observer) | ||
} | ||
}); | ||
} | ||
|
||
/** @type {RtdSubmodule} */ | ||
export const subModuleObj = { | ||
name: MODULE_NAME, | ||
init, | ||
getBidRequestData, | ||
markViewed | ||
}; | ||
|
||
submodule('realTimeData', subModuleObj); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
# Overview | ||
|
||
Module Name: Dynamic Ad Boost | ||
Module Type: Track when a adunit is viewable | ||
Maintainer: [email protected] | ||
|
||
# Description | ||
|
||
Enhance your revenue with the cutting-edge DynamicAdBoost module! By seamlessly integrating the powerful LuponMedia technology, our module retrieves adunits viewability data, providing publishers with valuable insights to optimize their revenue streams. To unlock the full potential of this technology, we provide a customized LuponMedia module tailored to your specific site requirements. Boost your ad revenue and gain unprecedented visibility into your performance with our advanced solution. | ||
|
||
In order to utilize this module, it is essential to collaborate with [LuponMedia](https://www.luponmedia.com/) to create an account and obtain detailed guidelines on configuring your sites. Working hand in hand with LuponMedia will ensure a smooth integration process, enabling you to fully leverage the capabilities of this module on your website. Take the first step towards optimizing your ad revenue and enhancing your site's performance by partnering with LuponMedia for a seamless experience. | ||
Contact [email protected] for information. | ||
|
||
## Building Prebid with Real-time Data Support | ||
|
||
First, make sure to add the Dynamic AdBoost submodule to your Prebid.js package with: | ||
|
||
`gulp build --modules=rtdModule,dynamicAdBoostRtdProvider` | ||
|
||
The following configuration parameters are available: | ||
|
||
``` | ||
pbjs.setConfig( | ||
... | ||
realTimeData: { | ||
auctionDelay: 2000, | ||
dataProviders: [ | ||
{ | ||
name: "dynamicAdBoost", | ||
params: { | ||
keyId: "[PROVIDED_KEY]", // Your provided Dynamic AdBoost keyId | ||
adUnits: ["allowedAdUnit1", "allowedAdUnit2"], | ||
threshold: 35 // optional | ||
} | ||
} | ||
] | ||
} | ||
... | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import { subModuleObj as rtdProvider } from 'modules/dynamicAdBoostRtdProvider.js'; | ||
import { loadExternalScript } from '../../../src/adloader.js'; | ||
import { expect } from 'chai'; | ||
|
||
const configWithParams = { | ||
params: { | ||
keyId: 'dynamic', | ||
adUnits: ['gpt-123'], | ||
threshold: 1 | ||
} | ||
}; | ||
|
||
const configWithoutRequiredParams = { | ||
params: { | ||
keyId: '' | ||
} | ||
}; | ||
|
||
describe('dynamicAdBoost', function() { | ||
let clock; | ||
let sandbox; | ||
beforeEach(function () { | ||
sandbox = sinon.sandbox.create(); | ||
clock = sandbox.useFakeTimers(Date.now()); | ||
}); | ||
afterEach(function () { | ||
sandbox.restore(); | ||
}); | ||
describe('init', function() { | ||
describe('initialize without expected params', function() { | ||
it('fails initalize when keyId is not present', function() { | ||
expect(rtdProvider.init(configWithoutRequiredParams)).to.be.false; | ||
}) | ||
}) | ||
|
||
describe('initialize with expected params', function() { | ||
it('successfully initialize with load script', function() { | ||
expect(rtdProvider.init(configWithParams)).to.be.true; | ||
clock.tick(1000); | ||
expect(loadExternalScript.called).to.be.true; | ||
}) | ||
}); | ||
}); | ||
}) | ||
|
||
describe('markViewed tests', function() { | ||
let sandbox; | ||
const mockObserver = { | ||
unobserve: sinon.spy() | ||
}; | ||
const makeElement = (id) => { | ||
const el = document.createElement('div'); | ||
el.setAttribute('id', id); | ||
return el; | ||
} | ||
const mockEntry = { | ||
target: makeElement('target_id') | ||
}; | ||
|
||
beforeEach(function() { | ||
sandbox = sinon.sandbox.create(); | ||
}) | ||
|
||
afterEach(function() { | ||
sandbox.restore() | ||
}) | ||
|
||
it('markViewed returns a function', function() { | ||
expect(rtdProvider.markViewed(mockEntry, mockObserver)).to.be.a('function') | ||
}); | ||
|
||
it('markViewed unobserves', function() { | ||
const func = rtdProvider.markViewed(mockEntry, mockObserver); | ||
func(); | ||
expect(mockObserver.unobserve.calledOnce).to.be.true; | ||
}); | ||
}) |