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

BLIINK Bid Adapter : Add new adapter #7299

Merged
merged 4 commits into from
Sep 9, 2021
Merged
Show file tree
Hide file tree
Changes from 3 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
312 changes: 312 additions & 0 deletions modules/bliinkBidAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,312 @@
// eslint-disable-next-line prebid/validate-imports
// eslint-disable-next-line prebid/validate-imports
import {registerBidder} from 'src/adapters/bidderFactory.js'

export const BIDDER_CODE = 'bliink'
export const BLIINK_ENDPOINT_ENGINE = 'https://engine.bliink.io/delivery'
export const BLIINK_ENDPOINT_ENGINE_VAST = 'https://engine.bliink.io/vast'
export const BLIINK_ENDPOINT_COOKIE_SYNC = 'https://cookiesync.api.bliink.io'
export const META_KEYWORDS = 'keywords'
export const META_DESCRIPTION = 'description'

const VIDEO = 'video'
const NATIVE = 'native'
const BANNER = 'banner'

const supportedMediaTypes = [BANNER, VIDEO, NATIVE]
const aliasBidderCode = ['bk']

export function getMetaList(name) {
if (!name || name.length === 0) return []

return [
{
key: 'name',
value: name,
},
{
key: 'name*',
value: name,
},
{
key: 'itemprop*',
value: name,
},
{
key: 'property',
value: `'og:${name}'`,
},
{
key: 'property',
value: `'twitter:${name}'`,
},
{
key: 'property',
value: `'article:${name}'`,
},
]
}

export function getOneMetaValue(query) {
const metaEl = document.querySelector(query)

if (metaEl && metaEl.content) {
return metaEl.content
}

return null
}

export function getMetaValue(name, type = 1) {
const metaList = getMetaList(name)
const value = metaList.some((meta) => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Array.some returns a boolean, not the value.

Probably best to just use a simple for-loop:

for (let i=0; i < metaList.length; i++) {
  const meta = metaList[i];
  const metaValue = getOneMetaValue(`meta[${meta.key}=${meta.value}]`);
  if (metaValue) {
    return metaValue
  }
}
return ''

Copy link
Contributor

Choose a reason for hiding this comment

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

Thank @gwhigs for pointing it out, we pushed the fix

const metaValue = getOneMetaValue(`meta[${meta.key}=${meta.value}]`)

if (metaValue) {
return metaValue
}
})
if (value) {
return value
}
return ''
}

export function getKeywords() {
const metaKeywords = getMetaValue(META_KEYWORDS)
if (metaKeywords) {
const keywords = [
...metaKeywords.split(','),
]

if (keywords && keywords.length > 0) {
return keywords
.filter((value) => value)
.map((value) => value.trim())
}
}

return []
}

export const parseXML = (content) => {
if (typeof content !== 'string' || content.length === 0) return null

const parser = new DOMParser()
const xml = parser.parseFromString(content, 'text/xml')

if (xml &&
xml.getElementsByTagName('VAST')[0] &&
xml.getElementsByTagName('VAST')[0].tagName === 'VAST') {
return xml
}

return null
}

/**
* @param bidRequest
* @param bliinkCreative
* @return {{cpm, netRevenue: boolean, ad: string, requestId, width: number, currency: string, mediaType: string, vastXml, ttl: number, height: number}|null}
*/
export const buildBid = (bidRequest, bliinkCreative) => {
if (!bidRequest && !bliinkCreative) return null

const body = {
requestId: bidRequest.bidId,
cpm: bliinkCreative.price,
creativeId: bliinkCreative.creativeId,
currency: 'EUR',
netRevenue: false,
width: 1,
height: 1,
ttl: 3600,
}

// eslint-disable-next-line no-mixed-operators
if ((bliinkCreative) && bidRequest &&
// eslint-disable-next-line no-mixed-operators
!bidRequest.bidId ||
!bidRequest.sizes ||
!bidRequest.params ||
!(bidRequest.params.placement)
) return null

delete bidRequest['bids']

return Object.assign(body, {
currency: bliinkCreative.currency,
width: 1,
height: 1,
mediaType: VIDEO,
ad: '<html lang="en"></html>',
vastXml: bliinkCreative.content,
})
}

/**
* @description Verify the the AdUnits.bids, respond with true (valid) or false (invalid).
*
* @param bid
* @return boolean
*/
export const isBidRequestValid = (bid) => {
return !(!bid || !bid.params || !bid.params.placement || !bid.params.tagId)
}

/**
* @description Takes an array of valid bid requests, all of which are guaranteed to have passed the isBidRequestValid() test.
*
* @param _[]
* @param bidderRequest
* @return {{ method: string, url: string } | null}
*/
export const buildRequests = (_, bidderRequest) => {
if (!bidderRequest) return null

let data = {
pageUrl: bidderRequest.refererInfo.referer,
pageDescription: getMetaValue(META_DESCRIPTION),
keywords: getKeywords().join(','),
pageTitle: document.title,
}

const endPoint = bidderRequest.bids[0].params.placement === VIDEO ? BLIINK_ENDPOINT_ENGINE_VAST : BLIINK_ENDPOINT_ENGINE

const params = {
bidderRequestId: bidderRequest.bidderRequestId,
bidderCode: bidderRequest.bidderCode,
bids: bidderRequest.bids,
refererInfo: bidderRequest.refererInfo,
}

if (bidderRequest.gdprConsent) {
data = Object.assign(data, {
gdpr: bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies,
gdpr_consent: bidderRequest.gdprConsent.consentString
})
}

if (bidderRequest.bids && bidderRequest.bids.length > 0 && bidderRequest.bids[0].sizes && bidderRequest.bids[0].sizes[0]) {
data = Object.assign(data, {
width: bidderRequest.bids[0].sizes[0][0],
height: bidderRequest.bids[0].sizes[0][1]
})

return {
method: 'GET',
url: `${endPoint}/${bidderRequest.bids[0].params.tagId}`,
data: data,
params: params,
}
}

return null
}

/**
* @description Parse the response (from buildRequests) and generate one or more bid objects.
*
* @param serverResponse
* @param request
* @return
*/
const interpretResponse = (serverResponse, request) => {
if ((serverResponse && serverResponse.mode === 'no-ad') && (!request.params)) {
return []
}

const body = serverResponse.body
const serverBody = request.params

const xml = parseXML(body)

if (xml) {
const price = xml.getElementsByTagName('Price') && xml.getElementsByTagName('Price')[0]
const currency = xml.getElementsByTagName('Currency') && xml.getElementsByTagName('Currency')[0]
const creativeId = xml.getElementsByTagName('CreativeId') && xml.getElementsByTagName('CreativeId')[0]

const creative = {
content: body,
price: (price && price.textContent) || 0,
currency: (currency && currency.textContent) || 'EUR',
creativeId: creativeId || 0,
media_type: 'video',
}

return buildBid(serverBody.bids[0], creative);
}

return []
}

/**
* @description If the publisher allows user-sync activity, the platform will call this function and the adapter may register pixels and/or iframe user syncs. For more information, see Registering User Syncs below
* @param syncOptions
* @param serverResponses
* @param gdprConsent
* @return {[{type: string, url: string}]|*[]}
*/
const getUserSyncs = (syncOptions, serverResponses, gdprConsent) => {
let syncs = []

if (syncOptions.pixelEnabled && serverResponses.length > 0) {
if (gdprConsent) {
const gdprParams = `consentString=${gdprConsent.consentString}`
const smartCallbackURL = encodeURIComponent(`${BLIINK_ENDPOINT_COOKIE_SYNC}/cookiesync?partner=smart&uid=[sas_uid]`)
const azerionCallbackURL = encodeURIComponent(`${BLIINK_ENDPOINT_COOKIE_SYNC}/cookiesync?partner=azerion&uid={PUB_USER_ID}`)
const appnexusCallbackURL = encodeURIComponent(`${BLIINK_ENDPOINT_COOKIE_SYNC}/cookiesync?partner=azerion&uid=$UID`)
return [
{
type: 'script',
url: 'https://prg.smartadserver.com/ac?out=js&nwid=3392&siteid=305791&pgname=rg&fmtid=81127&tgt=[sas_target]&visit=m&tmstp=[timestamp]&clcturl=[countgo]'
},
{
type: 'image',
url: `https://sync.smartadserver.com/getuid?nwid=3392&${gdprParams}&url=${smartCallbackURL}`,
},
{
type: 'image',
url: `https://ad.360yield.com/server_match?partner_id=1531&${gdprParams}&r=${azerionCallbackURL}`,
},
{
type: 'image',
url: `https://ads.stickyadstv.com/auto-user-sync?${gdprParams}`,
},
{
type: 'image',
url: `https://cookiesync.api.bliink.io/getuid?url=https%3A%2F%2Fvisitor.omnitagjs.com%2Fvisitor%2Fsync%3Fuid%3D1625272249969090bb9d544bd6d8d645%26name%3DBLIINK%26visitor%3D%24UID%26external%3Dtrue&${gdprParams}`,
},
{
type: 'image',
url: `https://cookiesync.api.bliink.io/getuid?url=https://pixel.advertising.com/ups/58444/sync?&gdpr=1&gdpr_consent=${gdprConsent.consentString}&redir=true&uid=$UID`,
},
{
type: 'image',
url: `https://ups.analytics.yahoo.com/ups/58499/occ?gdpr=1&gdpr_consent=${gdprConsent.consentString}`,
},
{
type: 'image',
url: `https://secure.adnxs.com/getuid?${appnexusCallbackURL}`,
},
]
}
}

return syncs;
}

/**
* @type {{interpretResponse: interpretResponse, code: string, aliases: string[], getUserSyncs: getUserSyncs, buildRequests: buildRequests, onTimeout: onTimeout, onSetTargeting: onSetTargeting, isBidRequestValid: isBidRequestValid, onBidWon: onBidWon}}
*/
export const spec = {
code: BIDDER_CODE,
aliases: aliasBidderCode,
supportedMediaTypes: supportedMediaTypes,
isBidRequestValid,
buildRequests,
interpretResponse,
getUserSyncs,
}

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

```
Module Name: BLIINK Bidder Adapter
Module Type: Bidder Adapter
Maintainer: [email protected] | [email protected]
gdpr_supported: true
tcf2_supported: true
media_types: banner, native, video
```

# Description

Module that connects to BLIINK demand sources to fetch bids.

# Test Parameters

## Sample Banner Ad Unit

```js
const adUnits = [
{
code: '/19968336/test',
mediaTypes: {
banner: {
sizes: [[300, 250]]
}
},
bids: [
{
bidder: 'bliink',
params: {
placement: 'banner',
tagId: '14f30eca-85d2-11e8-9eed-0242ac120007'
}
}
]
}
]
```

## Sample Instream Video Ad Unit

```js
const adUnits = [
{
code: '/19968336/prebid_cache_video_adunit',
sizes: [[640,480]],
mediaType: 'video',
mediaTypes: {
video: {
context: 'instream',
playerSize: [640, 480],
mimes: ['video/mp4'],
protocols: [1, 2, 3, 4, 5, 6, 7, 8],
playbackmethod: [2],
skip: 1
}
},
bids: [
{
bidder: 'bliink',
params: {
tagId: '41',
placement: 'video',
}
}
]
}
]
```
Loading