Skip to content

Commit

Permalink
Sharethrough - handle iframe bid param, safeframe support (prebid#2762)
Browse files Browse the repository at this point in the history
- if true, Sharethrough ad markup will not break out of iframe
- this also adds safeframe support
  • Loading branch information
jchau87 authored and AlessandroDG committed Sep 13, 2018
1 parent 53daf9d commit 0e845f3
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 42 deletions.
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

0 comments on commit 0e845f3

Please sign in to comment.