Skip to content

Commit

Permalink
DxKulture Bid Adapter : user syncs improvements (#10738)
Browse files Browse the repository at this point in the history
* Initial implementation of kulturemedia bid adapter

* Changing outstream to instream

* Enriching md file with test examples

* Changing nId to networkId

* Cleaning up md file

* Submitting rebranded dxkultureBidAdapter

* dxkultureBidAdapter - Improve UserSyncs

* Include gdpr/usp params in iframe usersync url

* Add gdpr/usp data to iframe usync urls

* Cleaning up testing html file

* Adding outstream support

* Updating exchange endpoint

* Resolve requests test

* Resolving iframe/pixel priority when iframeEnabled/pixelEnabled

* Improving userSync filtering condition

* Prioritize iframe user syncing

---------

Co-authored-by: Danijel Predarski <danijel.p@whitecitysoft.com>
Co-authored-by: dani-nova <73398187+dani-nova@users.noreply.github.com>
Co-authored-by: Slavisa Petkovic <trpeze@gmail.com>
Co-authored-by: Slavisa Petkovic <32300768+spetkovic@users.noreply.github.com>
  • Loading branch information
5 people authored Jan 2, 2024
1 parent ff27673 commit 233695d
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 83 deletions.
111 changes: 84 additions & 27 deletions modules/dxkultureBidAdapter.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
logInfo,
logWarn,
logError,
logMessage,
deepAccess,
Expand All @@ -8,13 +9,14 @@ import {
} from '../src/utils.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
import {BANNER, VIDEO} from '../src/mediaTypes.js';
import { Renderer } from '../src/Renderer.js';
import {ortbConverter} from '../libraries/ortbConverter/converter.js'

const BIDDER_CODE = 'dxkulture';
const DEFAULT_BID_TTL = 300;
const DEFAULT_NET_REVENUE = true;
const DEFAULT_CURRENCY = 'USD';
const SYNC_URL = 'https://ads.kulture.media/usync';
const DEFAULT_OUTSTREAM_RENDERER_URL = 'https://cdn.dxkulture.com/players/dxOutstreamPlayer.js';

const converter = ortbConverter({
context: {
Expand Down Expand Up @@ -55,20 +57,14 @@ const converter = ortbConverter({
},
bidResponse(buildBidResponse, bid, context) {
let resMediaType;
const {bidRequest} = context;

if (bid.adm?.trim().startsWith('<VAST')) {
resMediaType = VIDEO;
} else {
resMediaType = BANNER;
}

const isADomainPresent = bid.adomain && bid.adomain.length;

if (isADomainPresent) {
context.meta = {
advertiserDomains: bid.adomain
};
}

context.mediaType = resMediaType;
context.currency = DEFAULT_CURRENCY;

Expand All @@ -78,6 +74,10 @@ const converter = ortbConverter({

const bidResponse = buildBidResponse(bid, context);

if (resMediaType === VIDEO && bidRequest.mediaTypes.video.context === 'outstream') {
bidResponse.renderer = outstreamRenderer(bidResponse);
}

return bidResponse;
}
});
Expand All @@ -86,7 +86,7 @@ export const spec = {
code: BIDDER_CODE,
VERSION: '1.0.0',
supportedMediaTypes: [BANNER, VIDEO],
ENDPOINT: 'https://ads.kulture.media/pbjs',
ENDPOINT: 'https://ads.dxkulture.com/pbjs',

/**
* Determines whether or not the given bid request is valid.
Expand Down Expand Up @@ -138,33 +138,90 @@ export const spec = {

getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) {
logInfo('dxkulture.getUserSyncs', 'syncOptions', syncOptions, 'serverResponses', serverResponses);

let syncs = [];

if (!syncOptions.iframeEnabled && !syncOptions.pixelEnabled) {
return syncs;
}

if (syncOptions.iframeEnabled || syncOptions.pixelEnabled) {
let pixelType = syncOptions.iframeEnabled ? 'iframe' : 'image';
let queryParamStrings = [];
let syncUrl = SYNC_URL;
if (gdprConsent) {
queryParamStrings.push('gdpr=' + (gdprConsent.gdprApplies ? 1 : 0));
queryParamStrings.push('gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || ''));
}
if (uspConsent) {
queryParamStrings.push('us_privacy=' + encodeURIComponent(uspConsent));
serverResponses.forEach(resp => {
const userSync = deepAccess(resp, 'body.ext.usersync');
if (userSync) {
let syncDetails = [];
Object.keys(userSync).forEach(key => {
const value = userSync[key];
if (value.syncs && value.syncs.length) {
syncDetails = syncDetails.concat(value.syncs);
}
});
syncDetails.forEach(syncDetails => {
let queryParamStrings = [];
let syncUrl = syncDetails.url;

if (syncDetails.type === 'iframe') {
if (gdprConsent) {
queryParamStrings.push('gdpr=' + (gdprConsent.gdprApplies ? 1 : 0));
queryParamStrings.push('gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || ''));
}
if (uspConsent) {
queryParamStrings.push('us_privacy=' + encodeURIComponent(uspConsent));
}
syncUrl = `${syncDetails.url}${queryParamStrings.length > 0 ? '?' + queryParamStrings.join('&') : ''}`
}

syncs.push({
type: syncDetails.type === 'iframe' ? 'iframe' : 'image',
url: syncUrl
});
});

if (syncOptions.iframeEnabled) {
syncs = syncs.filter(s => s.type == 'iframe');
} else if (syncOptions.pixelEnabled) {
syncs = syncs.filter(s => s.type == 'image');
}
}
});
logInfo('dxkulture.getUserSyncs result=%o', syncs);
return syncs;
},

return [{
type: pixelType,
url: `${syncUrl}${queryParamStrings.length > 0 ? '?' + queryParamStrings.join('&') : ''}`
}];
}
};

function outstreamRenderer(bid) {
const rendererConfig = {
width: bid.width,
height: bid.height,
vastTimeout: 5000,
maxAllowedVastTagRedirects: 3,
allowVpaid: false,
autoPlay: true,
preload: true,
mute: false
}

};
const renderer = Renderer.install({
id: bid.adId,
url: DEFAULT_OUTSTREAM_RENDERER_URL,
config: rendererConfig,
loaded: false,
targetId: bid.adUnitCode,
adUnitCode: bid.adUnitCode
});

try {
renderer.setRender(function (bid) {
bid.renderer.push(() => {
const { id, config } = bid.renderer;
window.dxOutstreamPlayer(bid, id, config);
});
});
} catch (err) {
logWarn('dxkulture: Prebid Error calling setRender on renderer', err);
}

return renderer;
}

/* =======================================
* Util Functions
Expand Down
79 changes: 23 additions & 56 deletions test/spec/modules/dxkultureBidAdapter_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ describe('dxkultureBidAdapter', function() {

it('should return expected request object', function() {
const bidRequest = spec.buildRequests(bidderRequest.bids, bidderRequest);
expect(bidRequest.url).equal('https://ads.kulture.media/pbjs?pid=publisherId&placementId=123456');
expect(bidRequest.url).equal('https://ads.dxkulture.com/pbjs?pid=publisherId&placementId=123456');
expect(bidRequest.method).equal('POST');
});
});
Expand Down Expand Up @@ -606,7 +606,14 @@ describe('dxkultureBidAdapter', function() {
});
});

describe('user sync', function () {
describe('getUserSyncs', function () {
let bidRequest, bidderResponse;
beforeEach(function() {
const bidderRequest = getVideoRequest();
bidRequest = spec.buildRequests(bidderRequest.bids, bidderRequest);
bidderResponse = getBidderResponse();
});

it('handles no parameters', function () {
let opts = spec.getUserSyncs({});
expect(opts).to.be.an('array').that.is.empty;
Expand All @@ -617,66 +624,26 @@ describe('dxkultureBidAdapter', function() {
expect(opts).to.be.an('array').that.is.empty;
});

describe('when gdpr applies', function () {
let gdprConsent;
let gdprPixelUrl;
const consentString = 'gdpr-pixel-consent';
const gdprApplies = '1';
beforeEach(() => {
gdprConsent = {
consentString,
gdprApplies: true
};

gdprPixelUrl = `${SYNC_URL}&gdpr=${gdprApplies}&gdpr_consent=${consentString}`;
});
it('iframe sync enabled should return results', function () {
let opts = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: false}, [bidderResponse]);

it('when there is a response, it should have the gdpr query params', () => {
let [{url}] = spec.getUserSyncs(
{iframeEnabled: true, pixelEnabled: true},
[],
gdprConsent
);
expect(opts.length).to.equal(1);
expect(opts[0].type).to.equal('iframe');
expect(opts[0].url).to.equal(bidderResponse.body.ext.usersync['sovrn'].syncs[0].url);
});

expect(url).to.have.string(`gdpr_consent=${consentString}`);
expect(url).to.have.string(`gdpr=${gdprApplies}`);
});
it('pixel sync enabled should return results', function () {
let opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [bidderResponse]);

it('should not send signals if no consent object is available', function () {
let [{url}] = spec.getUserSyncs(
{iframeEnabled: true, pixelEnabled: true},
[],
);
expect(url).to.not.have.string('gdpr_consent=');
expect(url).to.not.have.string('gdpr=');
});
expect(opts.length).to.equal(1);
expect(opts[0].type).to.equal('image');
expect(opts[0].url).to.equal(bidderResponse.body.ext.usersync['appnexus'].syncs[0].url);
});

describe('when ccpa applies', function () {
let usPrivacyConsent;
let uspPixelUrl;
const privacyString = 'TEST';
beforeEach(() => {
usPrivacyConsent = 'TEST';
uspPixelUrl = `${SYNC_URL}&us_privacy=${privacyString}`
});
it('should send the us privacy string, ', () => {
let [{url}] = spec.getUserSyncs(
{iframeEnabled: true, pixelEnabled: true},
[],
undefined,
usPrivacyConsent
);
expect(url).to.have.string(`us_privacy=${privacyString}`);
});
it('all sync enabled should prioritize iframe', function () {
let opts = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, [bidderResponse]);

it('should not send signals if no consent string is available', function () {
let [{url}] = spec.getUserSyncs(
{iframeEnabled: true, pixelEnabled: true},
[],
);
expect(url).to.not.have.string('us_privacy=');
});
expect(opts.length).to.equal(1);
});
});
});

0 comments on commit 233695d

Please sign in to comment.