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

Widespace adapter #2283

Merged
merged 31 commits into from
Apr 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
4c0bbe8
First commit
mizmaar3 Feb 22, 2018
9a00f61
Readme added
mizmaar3 Feb 22, 2018
7441913
Renamed curr to currency
mizmaar3 Feb 22, 2018
102e153
Unit tests
mizmaar3 Feb 22, 2018
dd55ba9
Some cleanup
mizmaar3 Feb 22, 2018
44724dc
Merge branch 'master' into Widespace_adapter
mizmaar3 Feb 22, 2018
23cedb5
Localstorage test
mizmaar3 Feb 23, 2018
9670c5f
Perfermance data fix
mizmaar3 Feb 26, 2018
70c463c
Default currency to USD
mizmaar3 Feb 26, 2018
75b824f
Test userSync
mizmaar3 Feb 28, 2018
2a7d099
Merge branch 'master' into Widespace_adapter
mizmaar3 Feb 28, 2018
d92a5a5
Linter cleanup
mizmaar3 Feb 28, 2018
e0ff444
More params
mizmaar3 Mar 7, 2018
ffb52bb
Merge branch 'master' into Widespace_adapter
mizmaar3 Mar 7, 2018
2b823b1
Bid info test
mizmaar3 Mar 8, 2018
babef6e
Adding lcuid
mizmaar3 Mar 12, 2018
1b6e724
Lcuid corrected
mizmaar3 Mar 13, 2018
8380130
Connection info added
mizmaar3 Mar 13, 2018
d360387
Cookies added as fallback
mizmaar3 Mar 15, 2018
d5888a9
Cleanup
mizmaar3 Mar 15, 2018
c54e507
Data encoding when needed
mizmaar3 Mar 15, 2018
3af91ae
Completing tests
mizmaar3 Mar 15, 2018
25288fa
Debug options added
mizmaar3 Mar 16, 2018
34ea2d8
Readme changes
mizmaar3 Mar 16, 2018
791bb70
More readable try catch condition
mizmaar3 Mar 16, 2018
c85eaf3
Removing hb.demo prefix frm demo data
mizmaar3 Mar 19, 2018
54e5d4c
Merge branch 'master' into Widespace_adapter
mizmaar3 Mar 19, 2018
c246d9d
Empty lcuid if not available
mizmaar3 Mar 19, 2018
2ce021e
Superfluous argument to storeData() removed
mizmaar3 Mar 19, 2018
4441d94
EventListener removed as it is not allowed by pbjs
mizmaar3 Mar 20, 2018
24c309f
Test coverage increased
mizmaar3 Mar 23, 2018
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
239 changes: 239 additions & 0 deletions modules/widespaceBidAdapter.js
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];
Copy link
Contributor

@mike-chowla mike-chowla Mar 30, 2018

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?

Copy link
Contributor Author

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

Copy link
Contributor

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?

Copy link
Contributor Author

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 not set 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.

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}`;
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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);
40 changes: 40 additions & 0 deletions modules/widespaceBidAdapter.md
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'
}
}
}
]
}
];
```
Loading