diff --git a/libraries/vizionikUtils/vizionikUtils.js b/libraries/vizionikUtils/vizionikUtils.js
new file mode 100644
index 00000000000..6a544271ba3
--- /dev/null
+++ b/libraries/vizionikUtils/vizionikUtils.js
@@ -0,0 +1,141 @@
+import { hasPurpose1Consent } from '../../src/utils/gdpr.js';
+import { BANNER, VIDEO } from '../../src/mediaTypes.js';
+import { deepAccess, isArray, parseSizesInput } from '../../src/utils.js';
+
+export function getUserSyncs(syncEndpoint, paramNames) {
+ return function(syncOptions, serverResponses, gdprConsent, uspConsent) {
+ const syncs = [];
+
+ if (!hasPurpose1Consent(gdprConsent)) {
+ return syncs;
+ }
+
+ let params = `${paramNames?.usp ?? 'us_privacy'}=${uspConsent ?? ''}&${paramNames?.consent ?? 'gdpr_consent'}=${gdprConsent?.consentString ?? ''}`;
+
+ if (typeof gdprConsent?.gdprApplies === 'boolean') {
+ params += `&gdpr=${Number(gdprConsent.gdprApplies)}`;
+ }
+
+ if (syncOptions.iframeEnabled) {
+ syncs.push({
+ type: 'iframe',
+ url: `//${syncEndpoint}/match/sp.ifr?${params}`,
+ });
+ }
+
+ if (syncOptions.pixelEnabled) {
+ syncs.push({
+ type: 'image',
+ url: `//${syncEndpoint}/match/sp?${params}`,
+ });
+ }
+
+ return syncs;
+ }
+}
+
+export function sspInterpretResponse(ttl, adomain) {
+ return function(serverResponse, request) {
+ if (!serverResponse?.body?.content?.data) {
+ return [];
+ }
+
+ const bidResponses = [];
+ const body = serverResponse.body;
+
+ let mediaType = BANNER;
+ let ad, vastXml;
+ let width;
+ let height;
+
+ const sizes = getSize(body.size);
+ if (isArray(sizes)) {
+ [width, height] = sizes;
+ }
+
+ if (body.type.format != '') {
+ // banner
+ ad = body.content.data;
+ if (body.content.imps?.length) {
+ for (const imp of body.content.imps) {
+ ad += ``;
+ }
+ }
+ } else {
+ // video
+ vastXml = body.content.data;
+ mediaType = VIDEO;
+
+ if (!width || !height) {
+ const pSize = deepAccess(request.bidRequest, 'mediaTypes.video.playerSize');
+ const reqSize = getSize(pSize);
+ if (isArray(reqSize)) {
+ [width, height] = reqSize;
+ }
+ }
+ }
+
+ const bidResponse = {
+ requestId: request.bidRequest.bidId,
+ cpm: body.cpm,
+ currency: body.currency || 'USD',
+ width: parseInt(width),
+ height: parseInt(height),
+ creativeId: body.id,
+ netRevenue: true,
+ ttl: ttl,
+ ad: ad,
+ mediaType: mediaType,
+ vastXml: vastXml,
+ meta: {
+ advertiserDomains: [adomain],
+ }
+ };
+
+ if ((mediaType === VIDEO && request.bidRequest.mediaTypes?.video) || (mediaType === BANNER && request.bidRequest.mediaTypes?.banner)) {
+ bidResponses.push(bidResponse);
+ }
+
+ return bidResponses;
+ }
+}
+
+export function sspBuildRequests(defaultEndpoint) {
+ return function(validBidRequests, bidderRequest) {
+ const requests = [];
+ for (const bid of validBidRequests) {
+ const endpoint = bid.params.endpoint || defaultEndpoint;
+
+ requests.push({
+ method: 'GET',
+ url: `https://${endpoint}/get`,
+ data: {
+ site_id: bid.params.siteId,
+ placement_id: bid.params.placementId,
+ prebid: true,
+ },
+ bidRequest: bid,
+ });
+ }
+
+ return requests;
+ }
+}
+
+export function sspValidRequest(bid) {
+ const valid = bid.params.siteId && bid.params.placementId;
+
+ return !!valid;
+}
+
+function getSize(paramSizes) {
+ const parsedSizes = parseSizesInput(paramSizes);
+ const sizes = parsedSizes.map(size => {
+ const [width, height] = size.split('x');
+ const w = parseInt(width, 10);
+ const h = parseInt(height, 10);
+ return [w, h];
+ });
+
+ return sizes[0] || null;
+}
diff --git a/modules/digitalcaramelBidAdapter.js b/modules/digitalcaramelBidAdapter.js
new file mode 100644
index 00000000000..1f045fb352d
--- /dev/null
+++ b/modules/digitalcaramelBidAdapter.js
@@ -0,0 +1,21 @@
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+import { BANNER, VIDEO } from '../src/mediaTypes.js';
+import { getUserSyncs, sspBuildRequests, sspInterpretResponse, sspValidRequest } from '../libraries/vizionikUtils/vizionikUtils.js';
+
+const BIDDER_CODE = 'digitalcaramel';
+const DEFAULT_ENDPOINT = 'ssp-asr.digitalcaramel.com';
+const SYNC_ENDPOINT = 'sync.digitalcaramel.com';
+const ADOMAIN = 'digitalcaramel.com';
+const TIME_TO_LIVE = 360;
+
+export const spec = {
+ code: BIDDER_CODE,
+
+ isBidRequestValid: sspValidRequest,
+ buildRequests: sspBuildRequests(DEFAULT_ENDPOINT),
+ interpretResponse: sspInterpretResponse(TIME_TO_LIVE, ADOMAIN),
+ getUserSyncs: getUserSyncs(SYNC_ENDPOINT, {usp: 'usp', consent: 'consent'}),
+ supportedMediaTypes: [ BANNER, VIDEO ]
+}
+
+registerBidder(spec);
diff --git a/modules/digitalcaramelBidAdapter.md b/modules/digitalcaramelBidAdapter.md
new file mode 100644
index 00000000000..428e46c72fe
--- /dev/null
+++ b/modules/digitalcaramelBidAdapter.md
@@ -0,0 +1,46 @@
+# Overview
+
+```
+Module Name: Digitalcaramel Bid Adapter
+Module Type: Bidder Adapter
+Maintainer: tech@digitalcaramel.com
+```
+
+# Description
+Connects to Digitalcaramel server for bids.
+Module supports banner and video mediaType.
+
+# Test Parameters
+
+```
+ var adUnits = [{
+ code: '/test/div',
+ mediaTypes: {
+ banner: {
+ sizes: [[300, 250]]
+ }
+ },
+ bids: [{
+ bidder: 'digitalcaramel',
+ params: {
+ siteId: 'd1d83nbdi0fs73874a0g',
+ placementId: 'd1d8493di0fs73874a10'
+ }
+ }]
+ },
+ {
+ code: '/test/div',
+ mediaTypes: {
+ video: {
+ playerSize: [[640, 360]]
+ }
+ },
+ bids: [{
+ bidder: 'digitalcaramel',
+ params: {
+ siteId: 'd1d83nbdi0fs73874a0g',
+ placementId: 'd24v2ijdi0fs73874afg'
+ }
+ }]
+ },];
+```
diff --git a/modules/programmaticaBidAdapter.js b/modules/programmaticaBidAdapter.js
index efc9450d9b7..cb080864a7b 100644
--- a/modules/programmaticaBidAdapter.js
+++ b/modules/programmaticaBidAdapter.js
@@ -1,7 +1,6 @@
import { registerBidder } from '../src/adapters/bidderFactory.js';
import { BANNER, VIDEO } from '../src/mediaTypes.js';
-import { hasPurpose1Consent } from '../src/utils/gdpr.js';
-import { deepAccess, parseSizesInput, isArray } from '../src/utils.js';
+import { getUserSyncs, sspBuildRequests, sspInterpretResponse, sspValidRequest } from '../libraries/vizionikUtils/vizionikUtils.js';
const BIDDER_CODE = 'programmatica';
const DEFAULT_ENDPOINT = 'asr.programmatica.com';
@@ -12,142 +11,11 @@ const TIME_TO_LIVE = 360;
export const spec = {
code: BIDDER_CODE,
- isBidRequestValid: function(bid) {
- const valid = bid.params.siteId && bid.params.placementId;
-
- return !!valid;
- },
-
- buildRequests: function(validBidRequests, bidderRequest) {
- const requests = [];
- for (const bid of validBidRequests) {
- const endpoint = bid.params.endpoint || DEFAULT_ENDPOINT;
-
- requests.push({
- method: 'GET',
- url: `https://${endpoint}/get`,
- data: {
- site_id: bid.params.siteId,
- placement_id: bid.params.placementId,
- prebid: true,
- },
- bidRequest: bid,
- });
- }
-
- return requests;
- },
-
- interpretResponse: function(serverResponse, request) {
- if (!serverResponse?.body?.content?.data) {
- return [];
- }
-
- const bidResponses = [];
- const body = serverResponse.body;
-
- let mediaType = BANNER;
- let ad, vastXml;
- let width;
- let height;
-
- const sizes = getSize(body.size);
- if (isArray(sizes)) {
- [width, height] = sizes;
- }
-
- if (body.type.format != '') {
- // banner
- ad = body.content.data;
- if (body.content.imps?.length) {
- for (const imp of body.content.imps) {
- ad += ``;
- }
- }
- } else {
- // video
- vastXml = body.content.data;
- mediaType = VIDEO;
-
- if (!width || !height) {
- const pSize = deepAccess(request.bidRequest, 'mediaTypes.video.playerSize');
- const reqSize = getSize(pSize);
- if (isArray(reqSize)) {
- [width, height] = reqSize;
- }
- }
- }
-
- const bidResponse = {
- requestId: request.bidRequest.bidId,
- cpm: body.cpm,
- currency: body.currency || 'USD',
- width: parseInt(width),
- height: parseInt(height),
- creativeId: body.id,
- netRevenue: true,
- ttl: TIME_TO_LIVE,
- ad: ad,
- mediaType: mediaType,
- vastXml: vastXml,
- meta: {
- advertiserDomains: [ADOMAIN],
- }
- };
-
- if ((mediaType === VIDEO && request.bidRequest.mediaTypes?.video) || (mediaType === BANNER && request.bidRequest.mediaTypes?.banner)) {
- bidResponses.push(bidResponse);
- }
-
- return bidResponses;
- },
-
- getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) {
- const syncs = []
-
- if (!hasPurpose1Consent(gdprConsent)) {
- return syncs;
- }
-
- let params = `usp=${uspConsent ?? ''}&consent=${gdprConsent?.consentString ?? ''}`;
- if (typeof gdprConsent?.gdprApplies === 'boolean') {
- params += `&gdpr=${Number(gdprConsent.gdprApplies)}`;
- }
-
- if (syncOptions.iframeEnabled) {
- syncs.push({
- type: 'iframe',
- url: `//${SYNC_ENDPOINT}/match/sp.ifr?${params}`
- });
- }
-
- if (syncOptions.pixelEnabled) {
- syncs.push({
- type: 'image',
- url: `//${SYNC_ENDPOINT}/match/sp?${params}`
- });
- }
-
- return syncs;
- },
-
- onTimeout: function(timeoutData) {},
- onBidWon: function(bid) {},
- onSetTargeting: function(bid) {},
- onBidderError: function() {},
+ isBidRequestValid: sspValidRequest,
+ buildRequests: sspBuildRequests(DEFAULT_ENDPOINT),
+ interpretResponse: sspInterpretResponse(TIME_TO_LIVE, ADOMAIN),
+ getUserSyncs: getUserSyncs(SYNC_ENDPOINT, {usp: 'usp', consent: 'consent'}),
supportedMediaTypes: [ BANNER, VIDEO ]
}
registerBidder(spec);
-
-function getSize(paramSizes) {
- const parsedSizes = parseSizesInput(paramSizes);
- const sizes = parsedSizes.map(size => {
- const [width, height] = size.split('x');
- const w = parseInt(width, 10);
- const h = parseInt(height, 10);
- return [w, h];
- });
-
- return sizes[0] || null;
-}
diff --git a/modules/vistarsBidAdapter.js b/modules/vistarsBidAdapter.js
index 7f6606ca1c1..305d8141bcb 100644
--- a/modules/vistarsBidAdapter.js
+++ b/modules/vistarsBidAdapter.js
@@ -1,8 +1,8 @@
import { registerBidder } from '../src/adapters/bidderFactory.js';
import { BANNER, VIDEO } from '../src/mediaTypes.js';
-import { hasPurpose1Consent } from '../src/utils/gdpr.js';
import { deepSetValue, replaceAuctionPrice, deepClone, deepAccess } from '../src/utils.js';
import { ortbConverter } from '../libraries/ortbConverter/converter.js';
+import { getUserSyncs } from '../libraries/vizionikUtils/vizionikUtils.js';
const BIDDER_CODE = 'vistars';
const DEFAULT_ENDPOINT = 'ex-asr.vistarsagency.com';
@@ -76,34 +76,7 @@ export const spec = {
return bids;
},
- getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) {
- const syncs = []
-
- if (!hasPurpose1Consent(gdprConsent)) {
- return syncs;
- }
-
- let params = `us_privacy=${uspConsent || ''}&gdpr_consent=${gdprConsent?.consentString ? gdprConsent.consentString : ''}`;
- if (typeof gdprConsent?.gdprApplies === 'boolean') {
- params += `&gdpr=${Number(gdprConsent.gdprApplies)}`;
- }
-
- if (syncOptions.iframeEnabled) {
- syncs.push({
- type: 'iframe',
- url: `//${SYNC_ENDPOINT}/match/sp.ifr?${params}`
- });
- }
-
- if (syncOptions.pixelEnabled) {
- syncs.push({
- type: 'image',
- url: `//${SYNC_ENDPOINT}/match/sp?${params}`
- });
- }
-
- return syncs;
- },
+ getUserSyncs: getUserSyncs(SYNC_ENDPOINT),
supportedMediaTypes: [ BANNER, VIDEO ]
}
diff --git a/test/spec/modules/digitalcaramelBidAdapter_spec.js b/test/spec/modules/digitalcaramelBidAdapter_spec.js
new file mode 100644
index 00000000000..4b295ecd6bb
--- /dev/null
+++ b/test/spec/modules/digitalcaramelBidAdapter_spec.js
@@ -0,0 +1,263 @@
+import { expect } from 'chai';
+import { spec } from 'modules/digitalcaramelBidAdapter.js';
+import { deepClone } from 'src/utils.js';
+
+describe('digitalcaramelBidAdapterTests', function () {
+ const bidRequestData = {
+ bids: [
+ {
+ bidId: 'testbid',
+ bidder: 'digitalcaramel',
+ params: {
+ siteId: 'testsite',
+ placementId: 'testplacement',
+ },
+ sizes: [[300, 250]]
+ }
+ ]
+ };
+ const request = [];
+
+ it('validate_pub_params', function () {
+ expect(
+ spec.isBidRequestValid({
+ bidder: 'digitalcaramel',
+ params: {
+ siteId: 'testsite',
+ placementId: 'testplacement',
+ }
+ })
+ ).to.equal(true);
+ });
+
+ it('validate_generated_url', function () {
+ const request = spec.buildRequests(deepClone(bidRequestData.bids), { timeout: 1234 });
+ const req_url = request[0].url;
+
+ expect(req_url).to.equal('https://ssp-asr.digitalcaramel.com/get');
+ });
+
+ it('validate_response_params', function () {
+ const serverResponse = {
+ body: {
+ 'id': 'crid',
+ 'type': {
+ 'format': 'Image',
+ 'source': 'passback',
+ 'dspId': '',
+ 'dspCreativeId': ''
+ },
+ 'content': {
+ 'data': 'test ad',
+ 'imps': null,
+ 'click': {
+ 'url': '',
+ 'track': null
+ }
+ },
+ 'size': '300x250',
+ 'matching': '',
+ 'cpm': 10,
+ 'currency': 'USD'
+ }
+ };
+
+ const bidRequest = deepClone(bidRequestData.bids)
+ bidRequest[0].mediaTypes = {
+ banner: {}
+ }
+
+ const request = spec.buildRequests(bidRequest);
+ const bids = spec.interpretResponse(serverResponse, request[0]);
+ expect(bids).to.have.lengthOf(1);
+
+ const bid = bids[0];
+ expect(bid.ad).to.equal('test ad');
+ expect(bid.cpm).to.equal(10);
+ expect(bid.currency).to.equal('USD');
+ expect(bid.width).to.equal(300);
+ expect(bid.height).to.equal(250);
+ expect(bid.creativeId).to.equal('crid');
+ expect(bid.meta.advertiserDomains).to.deep.equal(['digitalcaramel.com']);
+ });
+
+ it('validate_response_params_imps', function () {
+ const serverResponse = {
+ body: {
+ 'id': 'crid',
+ 'type': {
+ 'format': 'Image',
+ 'source': 'passback',
+ 'dspId': '',
+ 'dspCreativeId': ''
+ },
+ 'content': {
+ 'data': 'test ad',
+ 'imps': [
+ 'testImp'
+ ],
+ 'click': {
+ 'url': '',
+ 'track': null
+ }
+ },
+ 'size': '300x250',
+ 'matching': '',
+ 'cpm': 10,
+ 'currency': 'USD'
+ }
+ };
+
+ const bidRequest = deepClone(bidRequestData.bids)
+ bidRequest[0].mediaTypes = {
+ banner: {}
+ }
+
+ const request = spec.buildRequests(bidRequest);
+ const bids = spec.interpretResponse(serverResponse, request[0]);
+ expect(bids).to.have.lengthOf(1);
+
+ const bid = bids[0];
+ expect(bid.ad).to.equal('test ad');
+ expect(bid.cpm).to.equal(10);
+ expect(bid.currency).to.equal('USD');
+ expect(bid.width).to.equal(300);
+ expect(bid.height).to.equal(250);
+ expect(bid.creativeId).to.equal('crid');
+ expect(bid.meta.advertiserDomains).to.deep.equal(['digitalcaramel.com']);
+ })
+
+ it('validate_invalid_response', function () {
+ const serverResponse = {
+ body: {}
+ };
+
+ const bidRequest = deepClone(bidRequestData.bids)
+ bidRequest[0].mediaTypes = {
+ banner: {}
+ }
+
+ const request = spec.buildRequests(bidRequest);
+ const bids = spec.interpretResponse(serverResponse, request[0]);
+ expect(bids).to.have.lengthOf(0);
+ })
+
+ it('video_bid', function () {
+ const bidRequest = deepClone(bidRequestData.bids);
+ bidRequest[0].mediaTypes = {
+ video: {
+ playerSize: [234, 765]
+ }
+ };
+
+ const request = spec.buildRequests(bidRequest, { timeout: 1234 });
+ const vastXml = '';
+ const serverResponse = {
+ body: {
+ 'id': 'cki2n3n6snkuulqutpf0',
+ 'type': {
+ 'format': '',
+ 'source': 'rtb',
+ 'dspId': '1'
+ },
+ 'content': {
+ 'data': vastXml,
+ 'imps': [
+ 'https://ssp-asr.dev.digitalcaramel.com/track/imp'
+ ],
+ 'click': {
+ 'url': '',
+ 'track': null
+ }
+ },
+ 'size': '',
+ 'matching': '',
+ 'cpm': 70,
+ 'currency': 'RUB'
+ }
+ };
+
+ const bids = spec.interpretResponse(serverResponse, request[0]);
+ expect(bids).to.have.lengthOf(1);
+
+ const bid = bids[0];
+ expect(bid.mediaType).to.equal('video');
+ expect(bid.vastXml).to.equal(vastXml);
+ expect(bid.width).to.equal(234);
+ expect(bid.height).to.equal(765);
+ });
+});
+
+describe('getUserSyncs', function() {
+ it('returns empty sync array', function() {
+ const syncOptions = {};
+
+ expect(spec.getUserSyncs(syncOptions)).to.deep.equal([]);
+ });
+
+ it('Should return array of objects with proper sync config , include CCPA', function() {
+ const syncData = spec.getUserSyncs({
+ pixelEnabled: true,
+ }, {}, {}, '1---');
+ expect(syncData).to.be.an('array').which.is.not.empty;
+ expect(syncData[0]).to.be.an('object')
+ expect(syncData[0].type).to.be.a('string')
+ expect(syncData[0].type).to.equal('image')
+ expect(syncData[0].url).to.be.a('string')
+ expect(syncData[0].url).to.equal('//sync.digitalcaramel.com/match/sp?usp=1---&consent=')
+ });
+
+ it('Should return array of objects with proper sync config , include GDPR', function() {
+ const syncData = spec.getUserSyncs({
+ iframeEnabled: true,
+ }, {}, {
+ gdprApplies: true,
+ consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw',
+ vendorData: {
+ purpose: {
+ consents: {
+ 1: true
+ },
+ },
+ }
+ }, '');
+ expect(syncData).to.be.an('array').which.is.not.empty;
+ expect(syncData[0]).to.be.an('object')
+ expect(syncData[0].type).to.be.a('string')
+ expect(syncData[0].type).to.equal('iframe')
+ expect(syncData[0].url).to.be.a('string')
+ expect(syncData[0].url).to.equal('//sync.digitalcaramel.com/match/sp.ifr?usp=&consent=COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw&gdpr=1')
+ });
+
+ it('Should return array of objects with proper sync config , include GDPR, no purpose', function() {
+ const syncData = spec.getUserSyncs({
+ iframeEnabled: true,
+ }, {}, {
+ gdprApplies: true,
+ consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw',
+ vendorData: {
+ purpose: {
+ consents: {
+ 1: false
+ },
+ },
+ }
+ }, '');
+ expect(syncData).is.empty;
+ });
+
+ it('Should return array of objects with proper sync config , GDPR not applies', function() {
+ const syncData = spec.getUserSyncs({
+ iframeEnabled: true,
+ }, {}, {
+ gdprApplies: false,
+ consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw',
+ }, '');
+ expect(syncData).to.be.an('array').which.is.not.empty;
+ expect(syncData[0]).to.be.an('object')
+ expect(syncData[0].type).to.be.a('string')
+ expect(syncData[0].type).to.equal('iframe')
+ expect(syncData[0].url).to.be.a('string')
+ expect(syncData[0].url).to.equal('//sync.digitalcaramel.com/match/sp.ifr?usp=&consent=COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw&gdpr=0')
+ });
+})