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
66 changes: 66 additions & 0 deletions modules/schain.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import {config} from '../src/config.js';
import {deepClone, logWarn} from '../src/utils.js';
import {normalizeFPD} from '../src/fpd/normalize.js';


export function applySchainConfig(ortb2Fragments) {
if (!ortb2Fragments) return ortb2Fragments;

let warned = false;
function warnDeprecated() {
if (!warned) {
logWarn('The schain module is deprecated and no longer needed; you may provide schain directly as FPD (e.g., "setConfig({ortb2: {source: {schain: {...}})")');
warned = true;
}
}

// Apply global schain config if available
// config's schain will have more precedence over ortb2.source.schain
const globalSchainConfig = config.getConfig('schain');
if (globalSchainConfig && globalSchainConfig.config) {
warnDeprecated();
if (!ortb2Fragments?.global?.source?.schain) {
applySchainToPath(ortb2Fragments, 'global.source', globalSchainConfig.config);
} else {
logWarn('Disregarding global schain config as schain is already provided in FPD')
}
}

// Apply bidder-specific schain configs
const bidderConfigs = config.getBidderConfig();
if (!bidderConfigs) return ortb2Fragments;

Object.entries(bidderConfigs)
.filter(([_, cfg]) => cfg.schain)
.forEach(([bidderCode, cfg]) => {
warnDeprecated();
const bidderPath = `bidder.${bidderCode}.source`;
const hasSchain = ortb2Fragments?.bidder?.[bidderCode]?.source?.schain;
if (!hasSchain) {
applySchainToPath(ortb2Fragments, bidderPath, cfg.schain?.config);
} else {
logWarn(`Disregarding schain config for bidder "${bidderCode}" as schain is already provided in FPD`);
}
});

return ortb2Fragments;
}

function applySchainToPath(fragments, path, schainConfig) {
const parts = path.split('.');
let current = fragments;

// Create path if it doesn't exist
parts.forEach(part => {
current[part] = current[part] || {};
current = current[part];
});

// Apply the schain config
current.schain = deepClone(schainConfig);
}

normalizeFPD.before((next, ortb2Fragments) => {
applySchainConfig(ortb2Fragments);
next(ortb2Fragments);
})
35 changes: 35 additions & 0 deletions modules/schain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# schain module

** DEPRECATED **.

This module is deprecated since prebid 10; schain may be provided directly as fpd, for example:

```typescript
pbjs.setConfig({
ortb2: {
source: {
schain: {
"ver":"1.0",
"complete": 1,
"nodes": [
{
"asi":"indirectseller.com",
"sid":"00001",
"hp":1
},

{
"asi":"indirectseller-2.com",
"sid":"00002",
"hp":1
}
]
}
}
}
})
```

You may also use the [FPD validation module](https://docs.prebid.org/dev-docs/modules/validationFpdModule.html) to validate your schain configuration.


12 changes: 0 additions & 12 deletions src/adapterManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ import {
import {getRefererInfo} from './refererDetection.js';
import {GDPR_GVLIDS, gdprDataHandler, gppDataHandler, uspDataHandler, } from './consentHandler.js';
import * as events from './events.js';
import {moveSchainToExt} from './fpd/schain.js';
import {EVENTS, S2S} from './constants.js';
import {useMetrics} from './utils/perfMetrics.js';
import {auctionManager} from './auctionManager.js';
Expand Down Expand Up @@ -309,15 +308,6 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a
const ortb2 = ortb2Fragments.global || {};
const bidderOrtb2 = ortb2Fragments.bidder || {};

function moveUserEidsToExt(o) {
const eids = o.user?.eids;
if (Array.isArray(eids) && eids.length) {
o.user.ext = o.user.ext || {};
o.user.ext.eids = [...(o.user.ext.eids || []), ...eids];
delete o.user.eids;
}
}

function addOrtb2(bidderRequest, s2sActivityParams) {
const redact = dep.redact(
s2sActivityParams != null
Expand All @@ -326,8 +316,6 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a
);

const merged = mergeDeep({source: {tid: auctionId}}, ortb2, bidderOrtb2[bidderRequest.bidderCode]);
moveUserEidsToExt(merged);
moveSchainToExt(merged, bidderOrtb2[bidderRequest.bidderCode]);
const fpd = Object.freeze(redact.ortb2(merged));
bidderRequest.ortb2 = fpd;
bidderRequest.bids = bidderRequest.bids.map((bid) => {
Expand Down
59 changes: 59 additions & 0 deletions src/fpd/normalize.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {deepEqual, deepSetValue, logWarn} from '../utils.js';
import {hook} from '../hook.js';

export const normalizeFPD = hook('sync', function(ortb2Fragments) {
[
normalizeEIDs,
normalizeSchain
].forEach(normalizer => applyNormalizer(normalizer, ortb2Fragments));
return ortb2Fragments;
})

function applyNormalizer(normalizer, ortb2Fragments) {
ortb2Fragments.global = normalizer(ortb2Fragments.global, 'global FPD');
Object.entries(ortb2Fragments.bidder).forEach(([bidder, ortb2]) => {
ortb2Fragments.bidder[bidder] = normalizer(ortb2, `bidder '${bidder}' FPD`);
})
}

export function normalizeEIDs(target, context) {
if (!target) return target;
const seen = [];
const eids = [
...(target?.user?.eids ?? []).map(eid => [0, eid]),
...(target?.user?.ext?.eids ?? []).map(eid => [1, eid])
].filter(([source, eid]) => {
if (seen.findIndex(([candidateSource, candidateEid]) =>
source !== candidateSource && deepEqual(candidateEid, eid)
) > -1) {
logWarn(`Found duplicate EID in user.eids and user.ext.eids (${context})`, eid)
return false;
} else {
seen.push([source, eid]);
return true;
}
});
if (eids.length > 0) {
deepSetValue(target, 'user.ext.eids', eids.map(([_, eid]) => eid));
}
delete target?.user?.eids;
return target;
}


export function normalizeSchain(target, context) {
if (!target) return target;
const schain = target.source?.schain;
const extSchain = target.source?.ext?.schain;
if (schain != null && extSchain != null && !deepEqual(schain, extSchain)) {
logWarn(`Conflicting source.schain and source.ext.schain (${context}), preferring source.schain`, {
'source.schain': schain,
'source.ext.schain': extSchain
})
}
if ((schain ?? extSchain) != null) {
deepSetValue(target, 'source.ext.schain', schain ?? extSchain);
}
delete target.source?.schain;
return target;
}
85 changes: 0 additions & 85 deletions src/fpd/schain.js

This file was deleted.

5 changes: 2 additions & 3 deletions src/prebid.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import * as events from './events.js';
import {newMetrics, useMetrics} from './utils/perfMetrics.js';
import {defer, PbPromise} from './utils/promise.js';
import {enrichFPD} from './fpd/enrichment.js';
import {schainPrecedence} from './fpd/schain.js';
import {allConsent} from './consentHandler.js';
import {
insertLocatorFrame,
Expand All @@ -52,6 +51,7 @@ import { ORTB_BANNER_PARAMS } from './banner.js';
import { BANNER, VIDEO } from './mediaTypes.js';
import {delayIfPrerendering} from './utils/prerendering.js';
import { newBidder } from './adapters/bidderFactory.js';
import {normalizeFPD} from './fpd/normalize.js';

const pbjsInstance = getGlobal();
const { triggerUserSyncs } = userSync;
Expand Down Expand Up @@ -591,8 +591,7 @@ pbjsInstance.requestBids = (function() {
global: mergeDeep({}, config.getAnyConfig('ortb2') || {}, ortb2 || {}),
bidder: Object.fromEntries(Object.entries(config.getBidderConfig()).map(([bidder, cfg]) => [bidder, deepClone(cfg.ortb2)]).filter(([_, ortb2]) => ortb2 != null))
}
// Apply schain precedence rules before enrichment
ortb2Fragments = schainPrecedence(ortb2Fragments);
ortb2Fragments = normalizeFPD(ortb2Fragments);

return enrichFPD(PbPromise.resolve(ortb2Fragments.global)).then(global => {
ortb2Fragments.global = global;
Expand Down
91 changes: 91 additions & 0 deletions test/spec/fpd/normalize_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import {normalizeEIDs, normalizeFPD, normalizeSchain} from '../../../src/fpd/normalize.js';
import * as utils from '../../../src/utils';

describe('FPD normalization', () => {
let sandbox;
beforeEach(() => {
sandbox = sinon.createSandbox();
sandbox.stub(utils, 'logWarn');
});
afterEach(() => {
sandbox.restore();
})

describe('EIDs', () => {
it('should merge user.eids into user.ext.eids', () => {
const fpd = {
user: {
eids: [{source: 'idA'}],
ext: {eids: [{source: 'idB'}]}
}
};
const result = normalizeEIDs(fpd);
expect(result.user.eids).to.not.exist;
expect(result.user.ext.eids).to.deep.have.members([
{source: 'idA'},
{source: 'idB'}
])
});
it('should remove duplicates', () => {
const fpd = {
user: {
eids: [{source: 'id'}],
ext: {eids: [{source: 'id'}]}
}
}
expect(normalizeEIDs(fpd).user.ext.eids).to.eql([
{source: 'id'}
])
sinon.assert.called(utils.logWarn);
});
it('should NOT remove duplicates if they come from the same place', () => {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting edge case :)

const fpd = {
user: {
eids: [{source: 'id'}, {source: 'id'}]
}
}
expect(normalizeEIDs(fpd).user.ext.eids.length).to.eql(2);
});
it('should do nothing if there are no eids', () => {
expect(normalizeEIDs({})).to.eql({});
})
})
describe('schain', () => {
it('should move schain to ext.schain', () => {
const fpd = {
source: {
schain: 'foo'
}
}
expect(normalizeSchain(fpd)).to.deep.equal({
source: {
ext: {
schain: 'foo'
}
}
})
});
it('should warn on conflict', () => {
const fpd = {
source: {
schain: 'foo',
ext: {
schain: 'bar'
}
},
}
expect(normalizeSchain(fpd)).to.eql({
source: {
ext: {
schain: 'foo'
}
}
});
sinon.assert.called(utils.logWarn);
});

it('should do nothing if there is no schain', () => {
expect(normalizeSchain({})).to.eql({});
})
})
})
Loading