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

Sharethrough - handle iframe bid param, safeframe support #2762

Merged
Merged
Show file tree
Hide file tree
Changes from all 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
95 changes: 71 additions & 24 deletions modules/sharethroughBidAdapter.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { registerBidder } from 'src/adapters/bidderFactory';

const VERSION = '3.0.0';
const BIDDER_CODE = 'sharethrough';
const VERSION = '2.0.0';
const STR_ENDPOINT = document.location.protocol + '//btlr.sharethrough.com/header-bid/v1';

export const sharethroughAdapterSpec = {
code: BIDDER_CODE,

isBidRequestValid: bid => !!bid.params.pkey && bid.bidder === BIDDER_CODE,

buildRequests: (bidRequests, bidderRequest) => {
return bidRequests.map(bid => {
let query = {
Expand All @@ -26,67 +28,112 @@ export const sharethroughAdapterSpec = {
query.consent_required = !!bidderRequest.gdprConsent.gdprApplies;
}

// Data that does not need to go to the server,
// but we need as part of interpretResponse()
const strData = {
stayInIframe: bid.params.iframe,
sizes: bid.sizes
}

return {
method: 'GET',
url: STR_ENDPOINT,
data: query
data: query,
strData: strData
};
})
},

interpretResponse: ({ body }, req) => {
if (!body || !Object.keys(body).length || !body.creatives.length) {
if (!body || !body.creatives || !body.creatives.length) {
return [];
}

const creative = body.creatives[0];
let size = [0, 0];
if (req.strData.stayInIframe) {
size = getLargestSize(req.strData.sizes);
}

return [{
requestId: req.data.bidId,
width: 0,
height: 0,
width: size[0],
height: size[1],
cpm: creative.cpm,
creativeId: creative.creative.creative_key,
deal_id: creative.creative.deal_id,
dealId: creative.creative.deal_id,
currency: 'USD',
netRevenue: true,
ttl: 360,
ad: generateAd(body, req)
}];
},

getUserSyncs: (syncOptions, serverResponses) => {
const syncs = [];
if (syncOptions.pixelEnabled && serverResponses.length > 0 && serverResponses[0].body) {
const shouldCookieSync = syncOptions.pixelEnabled &&
serverResponses.length > 0 &&
serverResponses[0].body &&
serverResponses[0].body.cookieSyncUrls;

if (shouldCookieSync) {
serverResponses[0].body.cookieSyncUrls.forEach(url => {
syncs.push({ type: 'image', url: url });
});
}

return syncs;
}
}

function getLargestSize(sizes) {
function area(size) {
return size[0] * size[1];
}

return sizes.reduce((prev, current) => {
if (area(current) > area(prev)) {
return current
} else {
return prev
}
}, [0, 0]);
}

function generateAd(body, req) {
const strRespId = `str_response_${req.data.bidId}`;

return `
let adMarkup = `
<div data-str-native-key="${req.data.placement_key}" data-stx-response-name="${strRespId}">
</div>
<script>var ${strRespId} = "${b64EncodeUnicode(JSON.stringify(body))}"</script>
<script src="//native.sharethrough.com/assets/sfp-set-targeting.js"></script>
<script>
(function() {
if (!(window.STR && window.STR.Tag) && !(window.top.STR && window.top.STR.Tag)) {
const sfp_js = document.createElement('script');
sfp_js.src = "//native.sharethrough.com/assets/sfp.js";
sfp_js.type = 'text/javascript';
sfp_js.charset = 'utf-8';
try {
window.top.document.getElementsByTagName('body')[0].appendChild(sfp_js);
} catch (e) {
console.log(e);
}
}
})()
</script>`;
`

if (req.strData.stayInIframe) {
// Don't break out of iframe
adMarkup = adMarkup + `<script src="//native.sharethrough.com/assets/sfp.js"></script>`
} else {
// Break out of iframe
adMarkup = adMarkup + `
<script src="//native.sharethrough.com/assets/sfp-set-targeting.js"></script>
<script>
(function() {
if (!(window.STR && window.STR.Tag) && !(window.top.STR && window.top.STR.Tag)) {
const sfp_js = document.createElement('script');
sfp_js.src = "//native.sharethrough.com/assets/sfp.js";
sfp_js.type = 'text/javascript';
sfp_js.charset = 'utf-8';
try {
window.top.document.getElementsByTagName('body')[0].appendChild(sfp_js);
} catch (e) {
console.log(e);
}
}
})()
</script>`
}

return adMarkup;
}

// See https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#The_Unicode_Problem
Expand Down
5 changes: 3 additions & 2 deletions modules/sharethroughBidAdapter.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,13 @@ Module that connects to Sharethrough's demand sources
]
},{
code: 'test-div',
sizes: [[1, 1]], // a mobile size
sizes: [[300,250], [1, 1]], // a mobile size
bids: [
{
bidder: "sharethrough",
params: {
pkey: 'LuB3vxGGFrBZJa6tifXW4xgK'
pkey: 'LuB3vxGGFrBZJa6tifXW4xgK',
iframe: true
}
}
]
Expand Down
87 changes: 71 additions & 16 deletions test/spec/modules/sharethroughBidAdapter_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,38 @@ const bidderRequest = [
sizes: [[700, 400]],
placementCode: 'bar',
params: {
pkey: 'bbbb2222'
pkey: 'bbbb2222',
iframe: true
}
}];
const prebidRequest = [{
method: 'GET',
url: document.location.protocol + '//btlr.sharethrough.com' + '/header-bid/v1',
data: {
bidId: 'bidId',
placement_key: 'pKey'
}
}];

const prebidRequests = [
{
method: 'GET',
url: document.location.protocol + '//btlr.sharethrough.com' + '/header-bid/v1',
data: {
bidId: 'bidId',
placement_key: 'pKey'
},
strData: {
stayInIframe: false,
sizes: []
}
},
{
method: 'GET',
url: document.location.protocol + '//btlr.sharethrough.com' + '/header-bid/v1',
data: {
bidId: 'bidId',
placement_key: 'pKey'
},
strData: {
stayInIframe: true,
sizes: [[300, 250], [300, 300], [250, 250], [600, 50]]
}
},
];

const bidderResponse = {
body: {
'adserverRequestId': '40b6afd5-6134-4fbb-850a-bb8972a46994',
Expand All @@ -48,6 +69,7 @@ const bidderResponse = {
},
header: { get: (header) => header }
};

// Mirrors the one in modules/sharethroughBidAdapter.js as the function is unexported
const b64EncodeUnicode = (str) => {
return btoa(
Expand All @@ -56,6 +78,7 @@ const b64EncodeUnicode = (str) => {
return String.fromCharCode('0x' + p1);
}));
}

describe('sharethrough adapter spec', () => {
describe('.code', () => {
it('should return a bidder code of sharethrough', () => {
Expand Down Expand Up @@ -119,13 +142,27 @@ describe('sharethrough adapter spec', () => {

describe('.interpretResponse', () => {
it('returns a correctly parsed out response', () => {
expect(spec.interpretResponse(bidderResponse, prebidRequest[0])[0]).to.include(
expect(spec.interpretResponse(bidderResponse, prebidRequests[0])[0]).to.include(
{
width: 0,
height: 0,
cpm: 12.34,
creativeId: 'aCreativeId',
deal_id: 'aDealId',
dealId: 'aDealId',
currency: 'USD',
netRevenue: true,
ttl: 360,
});
});

it('returns a correctly parsed out response with largest size when strData.stayInIframe is true', () => {
expect(spec.interpretResponse(bidderResponse, prebidRequests[1])[0]).to.include(
{
width: 300,
height: 300,
cpm: 12.34,
creativeId: 'aCreativeId',
dealId: 'aDealId',
currency: 'USD',
netRevenue: true,
ttl: 360,
Expand All @@ -134,21 +171,21 @@ describe('sharethrough adapter spec', () => {

it('returns a blank array if there are no creatives', () => {
const bidResponse = { body: { creatives: [] } };
expect(spec.interpretResponse(bidResponse, prebidRequest[0])).to.be.an('array').that.is.empty;
expect(spec.interpretResponse(bidResponse, prebidRequests[0])).to.be.an('array').that.is.empty;
});

it('returns a blank array if body object is empty', () => {
const bidResponse = { body: {} };
expect(spec.interpretResponse(bidResponse, prebidRequest[0])).to.be.an('array').that.is.empty;
expect(spec.interpretResponse(bidResponse, prebidRequests[0])).to.be.an('array').that.is.empty;
});

it('returns a blank array if body is null', () => {
const bidResponse = { body: null };
expect(spec.interpretResponse(bidResponse, prebidRequest[0])).to.be.an('array').that.is.empty;
expect(spec.interpretResponse(bidResponse, prebidRequests[0])).to.be.an('array').that.is.empty;
});

it('correctly sends back a sfp script tag', () => {
const adMarkup = spec.interpretResponse(bidderResponse, prebidRequest[0])[0].ad;
it('correctly generates ad markup', () => {
const adMarkup = spec.interpretResponse(bidderResponse, prebidRequests[0])[0].ad;
let resp = null;

expect(() => btoa(JSON.stringify(bidderResponse))).to.throw();
Expand All @@ -163,6 +200,19 @@ describe('sharethrough adapter spec', () => {
expect(adMarkup).to.match(
/window.top.document.getElementsByTagName\('body'\)\[0\].appendChild\(sfp_js\);/)
});

it('correctly generates ad markup for staying in iframe', () => {
const adMarkup = spec.interpretResponse(bidderResponse, prebidRequests[1])[0].ad;
let resp = null;

expect(() => btoa(JSON.stringify(bidderResponse))).to.throw();
expect(() => resp = b64EncodeUnicode(JSON.stringify(bidderResponse))).not.to.throw();
expect(adMarkup).to.match(
/data-str-native-key="pKey" data-stx-response-name=\"str_response_bidId\"/);
expect(!!adMarkup.indexOf(resp)).to.eql(true);
expect(adMarkup).to.match(
/<script src="\/\/native.sharethrough.com\/assets\/sfp.js"><\/script>/);
});
});

describe('.getUserSyncs', () => {
Expand All @@ -183,6 +233,11 @@ describe('sharethrough adapter spec', () => {
expect(syncArray).to.be.an('array').that.is.empty;
});

it('returns an empty array if the body.cookieSyncUrls is missing', () => {
const syncArray = spec.getUserSyncs({ pixelEnabled: true }, [{ body: { creatives: ['creative'] } }]);
expect(syncArray).to.be.an('array').that.is.empty;
});

it('returns an empty array if pixels are not enabled', () => {
const syncArray = spec.getUserSyncs({ pixelEnabled: false }, serverResponses);
expect(syncArray).to.be.an('array').that.is.empty;
Expand Down