-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
Widespace adapter #2283
Widespace adapter #2283
Changes from all commits
4c0bbe8
9a00f61
7441913
102e153
dd55ba9
44724dc
23cedb5
9670c5f
70c463c
75b824f
2a7d099
d92a5a5
e0ff444
ffb52bb
2b823b1
babef6e
1b6e724
8380130
d360387
d5888a9
c54e507
3af91ae
25288fa
34ea2d8
791bb70
c85eaf3
54e5d4c
c246d9d
2ce021e
4441d94
24c309f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,239 @@ | ||
import { version } from '../package.json'; | ||
import { config } from 'src/config'; | ||
import { registerBidder } from 'src/adapters/bidderFactory'; | ||
import { | ||
cookiesAreEnabled, | ||
parseQueryStringParameters, | ||
parseSizesInput, | ||
getTopWindowReferrer | ||
} from 'src/utils'; | ||
|
||
const BIDDER_CODE = 'widespace'; | ||
const WS_ADAPTER_VERSION = '2.0.0'; | ||
const LOCAL_STORAGE_AVAILABLE = window.localStorage; | ||
const COOKIE_ENABLED = cookiesAreEnabled(); | ||
const LS_KEYS = { | ||
PERF_DATA: 'wsPerfData', | ||
LC_UID: 'wsLcuid', | ||
CUST_DATA: 'wsCustomData' | ||
}; | ||
|
||
let preReqTime = 0; | ||
|
||
export const spec = { | ||
code: BIDDER_CODE, | ||
|
||
supportedMediaTypes: ['banner'], | ||
|
||
isBidRequestValid: function(bid) { | ||
if (bid.params && bid.params.sid) { | ||
return true; | ||
} | ||
return false; | ||
}, | ||
|
||
buildRequests: function(validBidRequests) { | ||
let serverRequests = []; | ||
const REQUEST_SERVER_URL = getEngineUrl(); | ||
const DEMO_DATA_PARAMS = ['gender', 'country', 'region', 'postal', 'city', 'yob']; | ||
const PERF_DATA = getData(LS_KEYS.PERF_DATA).map(perf_data => JSON.parse(perf_data)); | ||
const CUST_DATA = getData(LS_KEYS.CUST_DATA, false)[0]; | ||
const LC_UID = getLcuid(); | ||
|
||
let isInHostileIframe = false; | ||
try { | ||
window.top.location.toString(); | ||
isInHostileIframe = false; | ||
} catch (e) { | ||
isInHostileIframe = true; | ||
} | ||
|
||
validBidRequests.forEach((bid, i) => { | ||
let data = { | ||
'screenWidthPx': screen && screen.width, | ||
'screenHeightPx': screen && screen.height, | ||
'adSpaceHttpRefUrl': getTopWindowReferrer(), | ||
'referer': (isInHostileIframe ? window : window.top).location.href.split('#')[0], | ||
'inFrame': 1, | ||
'sid': bid.params.sid, | ||
'lcuid': LC_UID, | ||
'vol': isInHostileIframe ? '' : visibleOnLoad(document.getElementById(bid.adUnitCode)), | ||
'hb': '1', | ||
'hb.cd': CUST_DATA ? encodedParamValue(CUST_DATA) : '', | ||
'hb.floor': bid.bidfloor || '', | ||
'hb.spb': i === 0 ? pixelSyncPossibility() : -1, | ||
'hb.ver': WS_ADAPTER_VERSION, | ||
'hb.name': `prebidjs-${version}`, | ||
'hb.bidId': bid.bidId, | ||
'hb.sizes': parseSizesInput(bid.sizes).join(','), | ||
'hb.currency': bid.params.cur || bid.params.currency || '' | ||
}; | ||
|
||
// Include demo data | ||
if (bid.params.demo) { | ||
DEMO_DATA_PARAMS.forEach((key) => { | ||
if (bid.params.demo[key]) { | ||
data[key] = bid.params.demo[key]; | ||
} | ||
}); | ||
} | ||
|
||
// Include performance data | ||
if (PERF_DATA[i]) { | ||
Object.keys(PERF_DATA[i]).forEach((perfDataKey) => { | ||
data[perfDataKey] = PERF_DATA[i][perfDataKey]; | ||
}); | ||
} | ||
|
||
// Include connection info if available | ||
const CONNECTION = navigator.connection || navigator.webkitConnection; | ||
if (CONNECTION && CONNECTION.type && CONNECTION.downlinkMax) { | ||
data['netinfo.type'] = CONNECTION.type; | ||
data['netinfo.downlinkMax'] = CONNECTION.downlinkMax; | ||
} | ||
|
||
// Include debug data when available | ||
if (!isInHostileIframe) { | ||
const DEBUG_AD = (window.top.location.hash.split('&').find((val) => { | ||
return val.includes('WS_DEBUG_FORCEADID'); | ||
}) || '').split('=')[1]; | ||
data.forceAdId = DEBUG_AD; | ||
} | ||
|
||
// Remove empty params | ||
Object.keys(data).forEach((key) => { | ||
if (data[key] === '' || data[key] === undefined) { | ||
delete data[key]; | ||
} | ||
}); | ||
|
||
serverRequests.push({ | ||
method: 'POST', | ||
options: { | ||
contentType: 'application/x-www-form-urlencoded' | ||
}, | ||
url: REQUEST_SERVER_URL, | ||
data: parseQueryStringParameters(data) | ||
}); | ||
}); | ||
preReqTime = Date.now(); | ||
return serverRequests; | ||
}, | ||
|
||
interpretResponse: function(serverResponse, request) { | ||
const responseTime = Date.now() - preReqTime; | ||
const successBids = serverResponse.body || []; | ||
let bidResponses = []; | ||
successBids.forEach((bid) => { | ||
storeData({ | ||
'perf_status': 'OK', | ||
'perf_reqid': bid.reqId, | ||
'perf_ms': responseTime | ||
}, `${LS_KEYS.PERF_DATA}${bid.reqId}`); | ||
if (bid.status === 'ad') { | ||
bidResponses.push({ | ||
requestId: bid.bidId, | ||
cpm: bid.cpm, | ||
width: bid.width, | ||
height: bid.height, | ||
creativeId: bid.adId, | ||
currency: bid.currency, | ||
netRevenue: Boolean(bid.netRev), | ||
ttl: bid.ttl, | ||
referrer: getTopWindowReferrer(), | ||
ad: bid.code | ||
}); | ||
} | ||
}); | ||
|
||
return bidResponses | ||
}, | ||
|
||
getUserSyncs: function(syncOptions, serverResponses = []) { | ||
let userSyncs = []; | ||
userSyncs = serverResponses.reduce((allSyncPixels, response) => { | ||
if (response && response.body && response.body[0]) { | ||
(response.body[0].syncPixels || []).forEach((url) => { | ||
allSyncPixels.push({type: 'image', url}); | ||
}); | ||
} | ||
return allSyncPixels; | ||
}, []); | ||
return userSyncs; | ||
} | ||
}; | ||
|
||
function storeData(data, name, stringify = true) { | ||
const value = stringify ? JSON.stringify(data) : data; | ||
if (LOCAL_STORAGE_AVAILABLE) { | ||
localStorage.setItem(name, value); | ||
return true; | ||
} else if (COOKIE_ENABLED) { | ||
const theDate = new Date(); | ||
const expDate = new Date(theDate.setMonth(theDate.getMonth() + 12)).toGMTString(); | ||
window.document.cookie = `${name}=${value};path=/;expires=${expDate}`; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This creates a cookie in the publisher's cookie space which seems like something the publisher should at least informed is happening. I don't see any mention in the docs that adapter creates cookies. However, I see other adapters doing this too and do not know of specific prohibition against it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes I havnt found any adapter doc mentioning it, also cookie is just fallback and we have seen that 99.9% chances are for localStorage to be used. |
||
return true; | ||
} | ||
} | ||
|
||
function getData(name, remove = true) { | ||
let data = []; | ||
if (LOCAL_STORAGE_AVAILABLE) { | ||
Object.keys(localStorage).filter((key) => { | ||
if (key.includes(name)) { | ||
data.push(localStorage.getItem(key)); | ||
if (remove) { | ||
localStorage.removeItem(key); | ||
} | ||
} | ||
}); | ||
} | ||
|
||
if (COOKIE_ENABLED) { | ||
document.cookie.split(';').forEach((item) => { | ||
let value = item.split('='); | ||
if (value[0].includes(name)) { | ||
data.push(value[1]); | ||
if (remove) { | ||
document.cookie = `${value[0]}=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;`; | ||
} | ||
} | ||
}); | ||
} | ||
return data; | ||
} | ||
|
||
function pixelSyncPossibility() { | ||
const userSync = config.getConfig('userSync'); | ||
return userSync && userSync.pixelEnabled && userSync.syncEnabled ? userSync.syncsPerBidder : -1; | ||
} | ||
|
||
function visibleOnLoad(element) { | ||
if (element && element.getBoundingClientRect) { | ||
const topPos = element.getBoundingClientRect().top; | ||
return topPos < screen.height && topPos >= window.top.pageYOffset ? 1 : 0; | ||
}; | ||
return ''; | ||
} | ||
|
||
function getLcuid() { | ||
let lcuid = getData(LS_KEYS.LC_UID, false)[0]; | ||
if (!lcuid) { | ||
const random = ('4' + new Date().getTime() + String(Math.floor(Math.random() * 1000000000))).substring(0, 18); | ||
storeData(random, LS_KEYS.LC_UID, false); | ||
lcuid = getData(LS_KEYS.LC_UID, false)[0]; | ||
} | ||
return lcuid; | ||
} | ||
|
||
function encodedParamValue(value) { | ||
const requiredStringify = typeof JSON.parse(JSON.stringify(value)) === 'object'; | ||
return encodeURIComponent(requiredStringify ? JSON.stringify(value) : value); | ||
} | ||
|
||
function getEngineUrl() { | ||
const ENGINE_URL = 'https://engine.widespace.com/map/engine/dynadreq'; | ||
return window.wisp && window.wisp.ENGINE_URL ? window.wisp.ENGINE_URL : ENGINE_URL; | ||
} | ||
|
||
registerBidder(spec); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
# Overview | ||
|
||
|
||
**Module Name:** Widespace Bidder Adapter. | ||
**Module Type:** Bidder Adapter. | ||
**Maintainer:** support@widespace.com | ||
|
||
|
||
# Description | ||
|
||
Widespace Bidder Adapter for Prebid.js. | ||
Banner and video formats are supported. | ||
|
||
# Test Parameters | ||
``` | ||
var adUnits = [ | ||
{ | ||
code: 'test-div', | ||
sizes: [[300, 250], [300, 300]], | ||
bids: [ | ||
{ | ||
bidder: 'widespace', | ||
params: { | ||
sid: '7b6589bf-95c8-4656-90b9-af9737bb9ad3', // Required | ||
currency: 'EUR', // Optional | ||
bidfloor: '0.5', // Optional | ||
demo: { // Optional | ||
gender: 'M', | ||
country: 'Sweden', | ||
region: 'Stockholm', | ||
postal: '15115', | ||
city: 'Stockholm', | ||
yob: '1984' | ||
} | ||
} | ||
} | ||
] | ||
} | ||
]; | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see a storeData call for CUST_DATA so wouldn't it always be empty?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will be set by our server and I will just pass it on in next request when available
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I couldn't see how it ever gets set to local storage. Am I just missing where that happens?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, you are not missing anything because
CUST_DATA
is notset
by adapter but rather by adCreative code. This allows us get some sort of feedback when our won bid creative is rendered. Example: - If we detected some problem when trying to render ad i.e. unable to measure inscreen for some reason ? we will store the reason. - Get device information i.e. fine grained device resolution for iPhone models which not available in user agent HTTP header, it helps us to deliver better creative next time.