Skip to content
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

DxKulture Bid Adapter : user syncs improvements #10738

Merged
merged 24 commits into from
Jan 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
aadcc5b
Initial implementation of kulturemedia bid adapter
dani-nova Feb 28, 2023
d2c56e7
Changing outstream to instream
dani-nova Feb 28, 2023
74c1a14
Enriching md file with test examples
dani-nova Mar 1, 2023
909476a
Changing nId to networkId
dani-nova Mar 1, 2023
0679940
Cleaning up md file
dani-nova Mar 2, 2023
d3dfc6f
Merge remote-tracking branch 'upstream/master' into kulturemedia-adapter
dani-nova Mar 2, 2023
6197325
Merge tag '8.8.0' into kulturemedia-adapter
kmdevops Aug 14, 2023
c3ef5b7
Submitting rebranded dxkultureBidAdapter
kmdevops Aug 14, 2023
610a2fe
Merge remote-tracking branch 'upstream/master' into kulturemedia-adapter
kmdevops Aug 14, 2023
5d8d923
Merge pull request #1 from kokisolutions/kulturemedia-adapter
dani-nova Aug 14, 2023
ec87a41
Merge remote-tracking branch 'upstream/master'
kmdevops Aug 14, 2023
22e2af8
Merge remote-tracking branch 'upstream/master'
spetkovic Sep 27, 2023
883170c
Merge remote-tracking branch 'upstream/master'
kmdevops Oct 6, 2023
8ab4104
Merge branch 'prebid:master' into master
spetkovic Nov 16, 2023
4a2f7c8
dxkultureBidAdapter - Improve UserSyncs
spetkovic Nov 16, 2023
4be33d6
Include gdpr/usp params in iframe usersync url
spetkovic Nov 16, 2023
b8c494a
Add gdpr/usp data to iframe usync urls
spetkovic Nov 16, 2023
10769e7
Cleaning up testing html file
kmdevops Nov 17, 2023
d2f9762
Adding outstream support
kmdevops Nov 28, 2023
956dc58
Updating exchange endpoint
kmdevops Nov 28, 2023
f0bad09
Resolve requests test
kmdevops Nov 28, 2023
e5967b7
Resolving iframe/pixel priority when iframeEnabled/pixelEnabled
kmdevops Dec 20, 2023
25f3b54
Improving userSync filtering condition
kmdevops Dec 20, 2023
b45c5db
Prioritize iframe user syncing
kmdevops Dec 22, 2023
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
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');
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I also dont think this is right which is why I suggested using an if/else for whichever should take priority. Not this suggests that if both pixel and iframe are enabled, neither condition will be true. Can you please update to an if/else giving one type priority over the other i.e. if (syncOptions.iframeEnabled) {...} else if (syncOptions.pixelEnabled) {...} which would give priority to iframe syncing or vice versa.

}
});
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);
});
});
});