Skip to content

Commit

Permalink
Illumin Bid Adapter: initial release (#10375)
Browse files Browse the repository at this point in the history
* MinuteMediaPlus Bid Adapter: pass gpp consent to userSync server.

* Illumin Bid Adapter: added adapter.

* remove changes that not relevant for this PR.

* change extract method.

* fix test
  • Loading branch information
saar120 committed Sep 27, 2023
1 parent 4f7fcc8 commit 94a3e5b
Show file tree
Hide file tree
Showing 3 changed files with 1,005 additions and 0 deletions.
336 changes: 336 additions & 0 deletions modules/illuminBidAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,336 @@
import {_each, deepAccess, parseSizesInput, parseUrl, uniques, isFn} from '../src/utils.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
import {BANNER, VIDEO} from '../src/mediaTypes.js';
import {getStorageManager} from '../src/storageManager.js';
import {config} from '../src/config.js';

const DEFAULT_SUB_DOMAIN = 'exchange';
const BIDDER_CODE = 'illumin';
const BIDDER_VERSION = '1.0.0';
const CURRENCY = 'USD';
const TTL_SECONDS = 60 * 5;
const UNIQUE_DEAL_ID_EXPIRY = 1000 * 60 * 15;
const storage = getStorageManager({bidderCode: BIDDER_CODE});

function getTopWindowQueryParams() {
try {
const parsedUrl = parseUrl(window.top.document.URL, {decodeSearchAsString: true});
return parsedUrl.search;
} catch (e) {
return '';
}
}

export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) {
return `https://${subDomain}.illumin.com`;
}

export function extractCID(params) {
return params.cId;
}

export function extractPID(params) {
return params.pId;
}

export function extractSubDomain(params) {
return params.subDomain;
}

function isBidRequestValid(bid) {
const params = bid.params || {};
return !!(extractCID(params) && extractPID(params));
}

function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) {
const {
params,
bidId,
userId,
adUnitCode,
schain,
mediaTypes,
ortb2Imp,
bidderRequestId,
bidRequestsCount,
bidderRequestsCount,
bidderWinsCount
} = bid;
let {bidFloor, ext} = params;
const hashUrl = hashCode(topWindowUrl);
const uniqueDealId = getUniqueDealId(hashUrl);
const cId = extractCID(params);
const pId = extractPID(params);
const subDomain = extractSubDomain(params);

const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid', deepAccess(bid, 'ortb2Imp.ext.data.pbadslot', ''));

if (isFn(bid.getFloor)) {
const floorInfo = bid.getFloor({
currency: 'USD',
mediaType: '*',
size: '*'
});

if (floorInfo.currency === 'USD') {
bidFloor = floorInfo.floor;
}
}

let data = {
url: encodeURIComponent(topWindowUrl),
uqs: getTopWindowQueryParams(),
cb: Date.now(),
bidFloor: bidFloor,
bidId: bidId,
referrer: bidderRequest.refererInfo.ref,
adUnitCode: adUnitCode,
publisherId: pId,
sizes: sizes,
uniqueDealId: uniqueDealId,
bidderVersion: BIDDER_VERSION,
prebidVersion: '$prebid.version$',
res: `${screen.width}x${screen.height}`,
schain: schain,
mediaTypes: mediaTypes,
gpid: gpid,
transactionId: ortb2Imp?.ext?.tid,
bidderRequestId: bidderRequestId,
bidRequestsCount: bidRequestsCount,
bidderRequestsCount: bidderRequestsCount,
bidderWinsCount: bidderWinsCount,
bidderTimeout: bidderTimeout
};

appendUserIdsToRequestPayload(data, userId);

const sua = deepAccess(bidderRequest, 'ortb2.device.sua');

if (sua) {
data.sua = sua;
}

if (bidderRequest.gdprConsent) {
if (bidderRequest.gdprConsent.consentString) {
data.gdprConsent = bidderRequest.gdprConsent.consentString;
}
if (bidderRequest.gdprConsent.gdprApplies !== undefined) {
data.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0;
}
}
if (bidderRequest.uspConsent) {
data.usPrivacy = bidderRequest.uspConsent;
}

if (bidderRequest.gppConsent) {
data.gppString = bidderRequest.gppConsent.gppString;
data.gppSid = bidderRequest.gppConsent.applicableSections;
} else if (bidderRequest.ortb2?.regs?.gpp) {
data.gppString = bidderRequest.ortb2.regs.gpp;
data.gppSid = bidderRequest.ortb2.regs.gpp_sid;
}

const dto = {
method: 'POST',
url: `${createDomain(subDomain)}/prebid/multi/${cId}`,
data: data
};

_each(ext, (value, key) => {
dto.data['ext.' + key] = value;
});

return dto;
}

function appendUserIdsToRequestPayload(payloadRef, userIds) {
let key;
_each(userIds, (userId, idSystemProviderName) => {
key = `uid.${idSystemProviderName}`;

switch (idSystemProviderName) {
case 'digitrustid':
payloadRef[key] = deepAccess(userId, 'data.id');
break;
case 'lipb':
payloadRef[key] = userId.lipbid;
break;
case 'parrableId':
payloadRef[key] = userId.eid;
break;
case 'id5id':
payloadRef[key] = userId.uid;
break;
default:
payloadRef[key] = userId;
}
});
}

function buildRequests(validBidRequests, bidderRequest) {
const topWindowUrl = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation;
const bidderTimeout = config.getConfig('bidderTimeout');
const requests = [];
validBidRequests.forEach(validBidRequest => {
const sizes = parseSizesInput(validBidRequest.sizes);
const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest, bidderTimeout);
requests.push(request);
});
return requests;
}

function interpretResponse(serverResponse, request) {
if (!serverResponse || !serverResponse.body) {
return [];
}
const {bidId} = request.data;
const {results} = serverResponse.body;

let output = [];

try {
results.forEach(result => {
const {
creativeId,
ad,
price,
exp,
width,
height,
currency,
metaData,
advertiserDomains,
mediaType = BANNER
} = result;
if (!ad || !price) {
return;
}

const response = {
requestId: bidId,
cpm: price,
width: width,
height: height,
creativeId: creativeId,
currency: currency || CURRENCY,
netRevenue: true,
ttl: exp || TTL_SECONDS,
};

if (metaData) {
Object.assign(response, {
meta: metaData
})
} else {
Object.assign(response, {
meta: {
advertiserDomains: advertiserDomains || []
}
})
}

if (mediaType === BANNER) {
Object.assign(response, {
ad: ad,
});
} else {
Object.assign(response, {
vastXml: ad,
mediaType: VIDEO
});
}
output.push(response);
});
return output;
} catch (e) {
return [];
}
}

function getUserSyncs(syncOptions, responses, gdprConsent = {}, uspConsent = '') {
let syncs = [];
const {iframeEnabled, pixelEnabled} = syncOptions;
const {gdprApplies, consentString = ''} = gdprConsent;

const cidArr = responses.filter(resp => deepAccess(resp, 'body.cid')).map(resp => resp.body.cid).filter(uniques);
const params = `?cid=${encodeURIComponent(cidArr.join(','))}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent(consentString || '')}&us_privacy=${encodeURIComponent(uspConsent || '')}`
if (iframeEnabled) {
syncs.push({
type: 'iframe',
url: `https://sync.illumin.com/api/sync/iframe/${params}`
});
}
if (pixelEnabled) {
syncs.push({
type: 'image',
url: `https://sync.illumin.com/api/sync/image/${params}`
});
}
return syncs;
}

export function hashCode(s, prefix = '_') {
const l = s.length;
let h = 0
let i = 0;
if (l > 0) {
while (i < l) {
h = (h << 5) - h + s.charCodeAt(i++) | 0;
}
}
return prefix + h;
}

export function getUniqueDealId(key, expiry = UNIQUE_DEAL_ID_EXPIRY) {
const storageKey = `u_${key}`;
const now = Date.now();
const data = getStorageItem(storageKey);
let uniqueId;

if (!data || !data.value || now - data.created > expiry) {
uniqueId = `${key}_${now.toString()}`;
setStorageItem(storageKey, uniqueId);
} else {
uniqueId = data.value;
}

return uniqueId;
}

export function getStorageItem(key) {
try {
return tryParseJSON(storage.getDataFromLocalStorage(key));
} catch (e) {
}

return null;
}

export function setStorageItem(key, value, timestamp) {
try {
const created = timestamp || Date.now();
const data = JSON.stringify({value, created});
storage.setDataInLocalStorage(key, data);
} catch (e) {
}
}

export function tryParseJSON(value) {
try {
return JSON.parse(value);
} catch (e) {
return value;
}
}

export const spec = {
code: BIDDER_CODE,
version: BIDDER_VERSION,
supportedMediaTypes: [BANNER, VIDEO],
isBidRequestValid,
buildRequests,
interpretResponse,
getUserSyncs
};

registerBidder(spec);
35 changes: 35 additions & 0 deletions modules/illuminBidAdapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Overview

**Module Name:** Illumin Bid Adapter

**Module Type:** Bidder Adapter

**Maintainer:** [email protected]

# Description

Module that connects to Illumin's demand sources.

# Test Parameters
```js
var adUnits = [
{
code: 'test-ad',
sizes: [[300, 250]],
bids: [
{
bidder: 'illumin',
params: {
cId: '562524b21b1c1f08117fc7f9',
pId: '59ac17c192832d0011283fe3',
bidFloor: 0.0001,
ext: {
param1: 'loremipsum',
param2: 'dolorsitamet'
}
}
}
]
}
];
```
Loading

0 comments on commit 94a3e5b

Please sign in to comment.