-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Adds Tech Lab Agentic Audiences #14626
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
db010a4
10439f1
9d09c9a
5637f67
d9afb51
ca0e24e
4c05e71
e6bb76d
a632c75
fd5db4d
21ebd4d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,151 @@ | ||
| /** | ||
| * Agentic Audience Adapter – injects Agentic Audiences (vector-based) signals into the OpenRTB request. | ||
| * Conforms to the OpenRTB community extension: | ||
| * {@link https://github.com/InteractiveAdvertisingBureau/openrtb/blob/main/extensions/community_extensions/agentic-audiences.md Agentic Audiences in OpenRTB} | ||
| * | ||
| * Context: {@link https://github.com/IABTechLab/agentic-audiences IABTechLab Agentic Audiences} | ||
| * | ||
| * The {@link module:modules/realTimeData} module is required | ||
| * @module modules/agenticAudienceAdapter | ||
| * @requires module:modules/realTimeData | ||
| */ | ||
|
|
||
| import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; | ||
| import { submodule } from '../src/hook.js'; | ||
| import { getStorageManager } from '../src/storageManager.js'; | ||
| import { logInfo, mergeDeep } from '../src/utils.js'; | ||
| import { VENDORLESS_GVLID } from '../src/consentHandler.js'; | ||
|
|
||
| /** | ||
| * @typedef {import('./rtdModule/index.js').RtdSubmodule} RtdSubmodule | ||
| */ | ||
|
|
||
| const REAL_TIME_MODULE = 'realTimeData'; | ||
| const MODULE_NAME = 'agenticAudience'; | ||
|
|
||
| export const storage = getStorageManager({ | ||
| moduleType: MODULE_TYPE_RTD, | ||
| moduleName: MODULE_NAME, | ||
| }); | ||
|
|
||
| function dataFromLocalStorage(key) { | ||
| return storage.localStorageIsEnabled() ? storage.getDataFromLocalStorage(key) : null; | ||
| } | ||
|
|
||
| function dataFromCookie(key) { | ||
| return storage.cookiesAreEnabled() ? storage.getCookie(key) : null; | ||
| } | ||
|
|
||
| /** | ||
| * Map a stored entry to an OpenRTB Segment (Agentic Audiences): id, name, ext.{ver, vector, dimension, model, type} | ||
| * Assumes storage matches the intended shape; fields are copied without validation or coercion. | ||
| * @param {Object} entry - Raw entry from storage `entries` array | ||
| * @returns {Object|null} | ||
| */ | ||
| export function mapEntryToOpenRtbSegment(entry) { | ||
| if (entry == null || typeof entry !== 'object') return null; | ||
|
|
||
| return { | ||
| id: entry.id, | ||
| name: entry.name, | ||
| ext: { | ||
| ver: entry.ver, | ||
| vector: entry.vector, | ||
| dimension: entry.dimension, | ||
| model: entry.model, | ||
| type: entry.type | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| function init(config, userConsent) { | ||
| const providers = config?.params?.providers; | ||
| if (!providers || typeof providers !== 'object' || Object.keys(providers).length === 0) { | ||
| return false; | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
| /** | ||
| * @param {Object} reqBidsConfigObj | ||
| * @param {function} callback | ||
| * @param {Object} config | ||
| * @param {Object} userConsent | ||
| */ | ||
| function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { | ||
| const providers = config?.params?.providers; | ||
| if (!providers || typeof providers !== 'object' || Object.keys(providers).length === 0) { | ||
| callback(); | ||
| return; | ||
| } | ||
|
|
||
| const data = []; | ||
| const providerKeys = Object.keys(providers); | ||
|
|
||
| for (let i = 0; i < providerKeys.length; i++) { | ||
| const provider = providerKeys[i]; | ||
| const providerParams = providers[provider]; | ||
| const storageKey = providerParams && providerParams.storageKey; | ||
| if (!storageKey) continue; | ||
|
|
||
| const segments = getSegmentsForStorageKey(storageKey); | ||
|
|
||
| if (segments && segments.length > 0) { | ||
| data.push({ | ||
| name: provider, | ||
| segment: segments | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| if (data.length === 0) { | ||
| callback(); | ||
| return; | ||
| } | ||
|
|
||
| const updated = { | ||
| user: { | ||
| data | ||
| } | ||
| }; | ||
|
|
||
| mergeDeep(reqBidsConfigObj.ortb2Fragments.global, updated); | ||
| callback(); | ||
| } | ||
|
|
||
| function tryParse(data) { | ||
| try { | ||
| return JSON.parse(atob(data)); | ||
| } catch (error) { | ||
| logInfo(error); | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| function getSegmentsForStorageKey(key) { | ||
| const storedData = dataFromLocalStorage(key) || dataFromCookie(key); | ||
|
|
||
| if (!storedData || typeof storedData !== 'string') { | ||
| return []; | ||
| } | ||
|
|
||
| const parsed = tryParse(storedData); | ||
|
|
||
| if (!parsed || typeof parsed !== 'object' || !Array.isArray(parsed.entries)) { | ||
| return []; | ||
| } | ||
|
|
||
| return parsed.entries | ||
| .map(entry => mapEntryToOpenRtbSegment(entry)) | ||
| .filter(seg => seg != null); | ||
| } | ||
|
|
||
| /** @type {RtdSubmodule} */ | ||
| export const agenticAudienceAdapterSubmodule = { | ||
| name: MODULE_NAME, | ||
| gvlid: VENDORLESS_GVLID, | ||
| init, | ||
| getBidRequestData | ||
| }; | ||
|
|
||
| submodule(REAL_TIME_MODULE, agenticAudienceAdapterSubmodule); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If a publisher builds with Useful? React with 👍 / 👎.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i added the module to |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On sites that enable
consentManagementTcfwithtcfControl, this submodule is treated as an unknown vendor because the registration object never setsgvlid.attachRealTimeDataProvider()only recordssubmodule.gvlid(modules/rtdModule/index.js:198-200), andtcfControlresolves that ID before allowing storage access and UFPD enrichment (modules/tcfControl.js:110-138,245-257). In that setup, the localStorage/cookie read and theuser.dataupdate are both blocked unless the publisher adds a manualgvlMapping, so the module effectively does nothing on GDPR traffic.Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have added VENDORLESS_GVLID