Skip to content
Open
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
3 changes: 2 additions & 1 deletion modules/.submodules.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"pubProvidedIdSystem",
"publinkIdSystem",
"pubmaticIdSystem",
"rediadsIdSystem",
"rewardedInterestIdSystem",
"sharedIdSystem",
"startioIdSystem",
Expand Down Expand Up @@ -141,4 +142,4 @@
"adplayerproVideoProvider"
]
}
}
}
180 changes: 180 additions & 0 deletions modules/rediadsIdSystem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/**
* This module adds Rediads ID support to the User ID module
* The {@link module:modules/userId} module is required.
* @module modules/rediadsIdSystem
* @requires module:modules/userId
*/

import { submodule } from '../src/hook.js';
import { VENDORLESS_GVLID } from '../src/consentHandler.js';
import { cyrb53Hash, generateUUID, isPlainObject, isStr } from '../src/utils.js';

/**
* @typedef {import('../modules/userId/index.js').Submodule} Submodule
* @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig
* @typedef {import('../modules/userId/index.js').ConsentData} ConsentData
* @typedef {import('../modules/userId/index.js').IdResponse} IdResponse
*/

const MODULE_NAME = 'rediadsId';
const DEFAULT_SOURCE = 'rediads.com';
const DEFAULT_ATYPE = 1;
const DEFAULT_EXPIRES_DAYS = 30;
const DEFAULT_REFRESH_SECONDS = 3600;

function normalizeStoredId(storedId) {
if (isPlainObject(storedId)) {
return storedId;
}

if (isStr(storedId)) {
try {
const parsed = JSON.parse(storedId);
if (isPlainObject(parsed)) {
return parsed;
}
} catch (e) {}
}
}

function getConsentHash(consentData = {}) {
return `hash_${cyrb53Hash(JSON.stringify({
gdpr: consentData.gdpr?.consentString || null,
gpp: consentData.gpp?.gppString || null,
usp: consentData.usp || null,
coppa: consentData.coppa === true
}))}`;
}

function hasTcfPurpose1Consent(gdprConsent) {
return gdprConsent?.vendorData?.purpose?.consents?.[1] === true ||
gdprConsent?.vendorData?.purposeConsents?.[1] === true;
}

function canWriteStorage(consentData = {}) {
if (consentData.coppa === true) {
return false;
}

const gdprConsent = consentData.gdpr;
if (gdprConsent?.gdprApplies === true) {
return isStr(gdprConsent?.consentString) &&
gdprConsent.consentString.length > 0 &&
hasTcfPurpose1Consent(gdprConsent);
}

return true;
}

function canShareId(consentData = {}) {
if (consentData.coppa === true) {
return false;
}

if (isStr(consentData.usp) && consentData.usp.charAt(2) === 'Y') {
return false;
}

return true;
}

function ensureStorageDefaults(config) {
if (isPlainObject(config?.storage)) {
if (config.storage.expires == null) {
config.storage.expires = DEFAULT_EXPIRES_DAYS;
}
if (config.storage.refreshInSeconds == null) {
config.storage.refreshInSeconds = DEFAULT_REFRESH_SECONDS;
}
}
}

function buildStoredId(config, consentData, existingId) {
const params = config?.params || {};
const source = params.source || DEFAULT_SOURCE;
const expiresDays = config?.storage?.expires ?? DEFAULT_EXPIRES_DAYS;
const refreshInSeconds = config?.storage?.refreshInSeconds ?? DEFAULT_REFRESH_SECONDS;
const now = Date.now();

return {
id: existingId || `ruid_${generateUUID()}`,
source,
atype: DEFAULT_ATYPE,
canShare: canShareId(consentData),
consentHash: getConsentHash(consentData),
refreshAfter: now + (refreshInSeconds * 1000),
expiresAt: now + (expiresDays * 24 * 60 * 60 * 1000)
};
}

/** @type {Submodule} */
export const rediadsIdSubmodule = {
name: MODULE_NAME,
gvlid: VENDORLESS_GVLID,

decode(value) {
const storedId = normalizeStoredId(value);
if (!isStr(storedId?.id) || storedId.id.length === 0) {
return undefined;
}

return {
rediadsId: {
uid: storedId.id,
source: storedId.source || DEFAULT_SOURCE,
atype: storedId.atype || DEFAULT_ATYPE,
ext: {
canShare: storedId.canShare !== false,
consentHash: storedId.consentHash,
refreshAfter: storedId.refreshAfter
}
}
};
},

getId(config, consentData, storedId) {
ensureStorageDefaults(config);

if (!canWriteStorage(consentData)) {
return undefined;
}

const normalized = normalizeStoredId(storedId);
return {
id: buildStoredId(config, consentData, normalized?.id)
};
},

extendId(config, consentData, storedId) {
ensureStorageDefaults(config);

if (!canWriteStorage(consentData)) {
return undefined;
}

const normalized = normalizeStoredId(storedId);
if (!isStr(normalized?.id) || normalized.id.length === 0) {
return this.getId(config, consentData, storedId);
}

return {
id: buildStoredId(config, consentData, normalized.id)
};
},

eids: {
rediadsId(values) {
return values
.filter((value) => isStr(value?.uid) && value.ext?.canShare !== false)
.map((value) => ({
source: value.source || DEFAULT_SOURCE,
uids: [{
id: value.uid,
atype: value.atype || DEFAULT_ATYPE
}]
}));
}
}
};

submodule('userId', rediadsIdSubmodule);
52 changes: 52 additions & 0 deletions modules/rediadsIdSystem.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
## Rediads User ID Submodule

The Rediads User ID submodule generates a first-party identifier in the browser, stores it through Prebid's `userId` framework, and exposes it through `bidRequest.userId` and `userIdAsEids`.

The module is vendorless for TCF enforcement, so Prebid applies purpose-level storage checks without requiring a separate vendor consent entry.

### Build the Module

```bash
gulp build --modules=userId,rediadsIdSystem
```

### Example Configuration

```javascript
pbjs.setConfig({
userSync: {
userIds: [{
name: 'rediadsId',
params: {
source: 'rediads.com'
},
storage: {
type: 'html5',
name: 'rediads_id',
expires: 30,
refreshInSeconds: 3600
}
}]
}
});
```

### Parameters

| Param under `userSync.userIds[]` | Scope | Type | Description | Example |
| --- | --- | --- | --- | --- |
| `name` | Required | String | Name of the module. | `'rediadsId'` |
| `params` | Optional | Object | Rediads-specific configuration. | |
| `params.source` | Optional | String | EID `source` value to emit. Defaults to `'rediads.com'`. | `'rediads.com'` |
| `storage.type` | Recommended | String | Prebid-managed storage type. | `'html5'` |
| `storage.name` | Recommended | String | Storage key used by Prebid. | `'rediads_id'` |
| `storage.expires` | Optional | Number | Days before the cached ID expires. Defaults to `30`. | `30` |
| `storage.refreshInSeconds` | Optional | Number | Seconds before the cached ID is refreshed. Defaults to `3600`. | `3600` |

### Behavior Notes

- `getId()` generates a `ruid_<uuid>` value on first use.
- `extendId()` preserves the existing Rediads ID and refreshes metadata.
- `decode()` exposes the ID as `bidRequest.userId.rediadsId`.
- EIDs are suppressed when US Privacy or GPP opt-out signals indicate sharing should be blocked.
- The module returns `undefined` when COPPA applies or when GDPR applies without TCF Purpose 1 consent.
8 changes: 8 additions & 0 deletions modules/userId/eids.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,14 @@ userIdAsEids = [
}]
},

{
source: 'rediads.com',
uids: [{
id: 'ruid_7b9c1d3f-1e2b-4e7b-9e5a-acde12345678',
atype: 1
}]
},

{
source: 'britepool.com',
uids: [{
Expand Down
12 changes: 12 additions & 0 deletions modules/userId/userId.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,18 @@ pbjs.setConfig({
{
name: "mygaruId"
},
{
name: "rediadsId",
params: {
source: "rediads.com"
},
storage: {
type: "html5",
name: "rediads_id",
expires: 30,
refreshInSeconds: 3600
}
},
{
name: "startioId"
}
Expand Down
Loading
Loading