Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
40 changes: 40 additions & 0 deletions prebid-module/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Prebid module (vendor example)

This folder contains an **example** Real-Time Data (RTD) submodule you can copy and adapt. It shows how Agentic Audiences signals can be read from browser storage and merged into the global ORTB2 fragment as `user.data`, in line with the OpenRTB community extension [**Agentic Audiences in OpenRTB**](https://github.com/InteractiveAdvertisingBureau/openrtb/blob/main/extensions/community_extensions/agentic-audiences.md).

**Do not ship this copy as-is under the sample names.** Vendors are expected to **rename the module file**, align **submodule / build identifiers** with that name, and set **`gvlid`** to their own Global Vendor List ID.

For the **canonical, maintained module** (build flags, publisher configuration, storage format, and behavior), use the implementation and documentation in the **[Prebid.js](https://github.com/prebid/Prebid.js)** repository—not this repo.

## Initiative context

- [IAB Tech Lab — Agentic Audiences](https://iabtechlab.com/standards/agentic-audiences/)
- [IABTechLab/agentic-audiences](https://github.com/IABTechLab/agentic-audiences)

The upstream reference implementation was introduced in [Prebid.js #14626](https://github.com/prebid/Prebid.js/pull/14626).

## What vendors should change

When you implement your own module from this example:

1. **Rename the module file** (e.g. `yourVendorAgenticRtd.js`) and update imports/paths everywhere it is referenced (including the matching test file name).
2. Set **`MODULE_NAME`** (and any exported submodule name) to a string that matches your RTD data provider name and your branding—keep it consistent with `modules/.submodules.json` and your docs.
3. Replace the **`gvlid`** placeholder with your vendor’s numeric GVL ID (see inline comment in the module source).
4. Register the build name in **`modules/.submodules.json`** (see `integration/submodules.json.snippet` for the pattern—your entry will use **your** module name, not the example’s).
5. Adjust **`DEFAULT_STORAGE_KEY`** / **`params.storageKey`** behavior if your integration uses different storage conventions.
6. Update or replace the unit test file so module paths and names match your fork.

After that, follow **[Prebid.js contributing guidelines](https://github.com/prebid/Prebid.js/blob/master/CONTRIBUTING.md)** if you intend to open a pull request against Prebid.js, and point publishers to **Prebid.org / the Prebid.js repo** for how to build and configure the bundle.

## Layout (this example)

| Path | Role |
| --- | --- |
| `modules/agenticAudienceAdapter.js` | Example RTD submodule—**rename and edit** for your vendor module |
| `test/spec/modules/agenticAudienceAdapter_spec.js` | Example tests—**rename and align** with your module |
| `integration/submodules.json.snippet` | Illustrative `.submodules.json` entry pattern |
| `examples/publisher-config.example.js` | Illustrative only; real publisher setup lives with Prebid docs |

## Where usage is documented

- **Build, `setConfig`, and operational details:** [prebid/Prebid.js](https://github.com/prebid/Prebid.js) and [docs.prebid.org](https://docs.prebid.org/).
26 changes: 26 additions & 0 deletions prebid-module/examples/publisher-config.example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Example Prebid.js publisher configuration for the Agentic Audiences RTD submodule.
* Copy into your site’s Prebid bootstrap. Replace the GVL placeholder with your
* vendor’s numeric Global Vendor List ID (see README.md in this folder).
*/

pbjs.setConfig({
consentManagement: {
// ... your CMP / GDPR config as required
},
gvlMapping: {
// Key must match the RTD data provider name: "agenticAudience"
agenticAudience: 0, // TODO: replace 0 with your vendor’s numeric GVL ID (not a string)
},
realTimeData: {
dataProviders: [
{
name: 'agenticAudience',
params: {
// Optional: use a dedicated storage key for your integration
// storageKey: '_my_vendor_agentic_audience_',
},
},
],
},
});
1 change: 1 addition & 0 deletions prebid-module/integration/submodules.json.snippet
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"agenticAudienceAdapter",
142 changes: 142 additions & 0 deletions prebid-module/modules/agenticAudienceAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/**
* 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
*
* Injects one OpenRTB `Data` object into `user.data` (`name` = submodule id, `segment[]` from storage).
* Each segment has optional `id`/`name` and `ext.aa` with `ver`, `vector`, `dimension`, `model`, `type`.
* Storage is read from the default key (see `DEFAULT_STORAGE_KEY` export) unless `params.storageKey` is set.
*
* @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';

/**
* @typedef {import('./rtdModule/index.js').RtdSubmodule} RtdSubmodule
*/

const REAL_TIME_MODULE = 'realTimeData';
const MODULE_NAME = 'agenticAudience';

/** @type {string} Default localStorage / cookie key when `params.storageKey` is omitted. */
export const DEFAULT_STORAGE_KEY = '_agentic_audience_';

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.aa.{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: {
aa: {
ver: entry.ver,
vector: entry.vector,
dimension: entry.dimension,
model: entry.model,
type: entry.type
}
}
};
}

function init(config, userConsent) {
return true;
}

/**
* @param {Object} reqBidsConfigObj
* @param {function} callback
* @param {Object} config
* @param {Object} userConsent
*/
function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) {
const customKey = config?.params?.storageKey;
const storageKey =
typeof customKey === 'string' && customKey.length > 0 ? customKey : DEFAULT_STORAGE_KEY;

const segments = getSegmentsForStorageKey(storageKey);

if (!segments || segments.length === 0) {
callback();
return;
}

const updated = {
user: {
data: [
{
name: MODULE_NAME,
segment: segments
}
]
}
};

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: 0, // change this to your gvlid
init,
getBidRequestData
};

submodule(REAL_TIME_MODULE, agenticAudienceAdapterSubmodule);
Loading