Skip to content

Commit

Permalink
Retailspot Bid Adapter : initial release (#9824)
Browse files Browse the repository at this point in the history
* add bidder adapter

* fix env url

* add unit tests

* minor: fix bids access in request

* add details in bid response

* fix unit tests

* fix adId undefined case

* reset package lock json

* handle vastXml as base64 string
  • Loading branch information
rs-guian authored May 8, 2023
1 parent 9f1dd22 commit ca2f974
Show file tree
Hide file tree
Showing 3 changed files with 644 additions and 0 deletions.
32 changes: 32 additions & 0 deletions modules/retailspotBidAdapter .md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Overview

Module Name: RetailSpot Bidder Adapter
Module Type: Bidder Adapter
Maintainer: guillaume@retail-spot.io

# Description

Module that connects to RetailSpot demand sources.
Banner and Video ad formats are supported.

# Test Parameters
```
var adUnits = {
"code": "test-div",
"mediaTypes": {
"banner": {
"sizes": ["300x250"]
},
"video": {
context: "instream",
playerSize: [[640, 480]]
}
},
bids: [{
bidder: "retailspot",
params: {
placement: "test-12345"
}
}]
};
```
186 changes: 186 additions & 0 deletions modules/retailspotBidAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import {buildUrl, deepAccess, parseSizesInput} from '../src/utils.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
import {BANNER, VIDEO} from '../src/mediaTypes.js';

const BIDDER_CODE = 'retailspot';
const DEFAULT_SUBDOMAIN = 'ssp';
const PREPROD_SUBDOMAIN = 'ssp-preprod';
const HOST = 'retail-spot.io';
const ENDPOINT = '/prebid';
const DEV_URL = 'http://localhost:8090/prebid';

export const spec = {
code: BIDDER_CODE,
supportedMediaTypes: [BANNER, VIDEO],
aliases: ['rs'], // short code
/**
* Determines whether or not the given bid request is valid.
*
* @param {BidRequest} bid The bid params to validate.
* @return boolean True if this is a valid bid, and false otherwise.
*/
isBidRequestValid: function (bid) {
const sizes = getSize(getSizeArray(bid));
const sizeValid = sizes.width > 0 && sizes.height > 0;

return deepAccess(bid, 'params.placement') && sizeValid;
},
/**
* Make a server request from the list of BidRequests.
*
* @param {bidRequests} - bidRequests.bids[] is an array of AdUnits and bids
* @return ServerRequest Info describing the request to the server.
*/
buildRequests: function (bidRequests, bidderRequest) {
const payload = bidderRequest;
payload.rs_pbjs_version = '$prebid.version$';

const data = JSON.stringify(payload);
const options = {
withCredentials: true
};

const envParam = bidRequests[0].params.env;
var subDomain = DEFAULT_SUBDOMAIN;
if (envParam === 'preprod') {
subDomain = PREPROD_SUBDOMAIN;
}

let url = buildUrl({
protocol: 'https',
host: `${subDomain}.${HOST}`,
pathname: ENDPOINT
});

if (envParam === 'dev') {
url = DEV_URL;
}

return {
method: 'POST',
url,
data,
options
};
},
/**
* Unpack the response from the server into a list of bids.
*
* @param {*} serverResponse A successful response from the server.
* @return {Bid[]} An array of bids which were nested inside the server.
*/
interpretResponse: function (serverResponse, request) {
const bidResponses = [];
var bidRequests = {};

try {
bidRequests = JSON.parse(request.data).bids;
} catch (err) {
// json error initial request can't be read
}

// For this adapter, serverResponse is a list
serverResponse.body.forEach(response => {
const bid = createBid(response, bidRequests);
if (bid) {
bidResponses.push(bid);
}
});

return bidResponses;
}
}

function getSizeArray(bid) {
let inputSize = bid.sizes || [];

if (bid.mediaTypes && bid.mediaTypes.banner) {
inputSize = bid.mediaTypes.banner.sizes || [];
}

// handle size in bid.params in formats: [w, h] and [[w,h]].
if (bid.params && Array.isArray(bid.params.size)) {
inputSize = bid.params.size;
if (!Array.isArray(inputSize[0])) {
inputSize = [inputSize]
}
}

return parseSizesInput(inputSize);
}

/* Get parsed size from request size */
function getSize(sizesArray) {
const parsed = {};
// the main requested size is the first one
const size = sizesArray[0];

if (typeof size !== 'string') {
return parsed;
}

const parsedSize = size.toUpperCase().split('X');
const width = parseInt(parsedSize[0], 10);
if (width) {
parsed.width = width;
}

const height = parseInt(parsedSize[1], 10);
if (height) {
parsed.height = height;
}

return parsed;
}

/* Create bid from response */
function createBid(response, bidRequests) {
if (!response || !response.mediaType ||
(response.mediaType === 'video' && !response.vastXml) ||
(response.mediaType === 'banner' && !response.ad)) {
return;
}

const request = bidRequests && bidRequests.length && bidRequests.find(itm => response.requestId === itm.bidId);
// In case we don't retreive the size from the adserver, use the given one.
if (request) {
if (!response.width || response.width === '0') {
response.width = request.width;
}

if (!response.height || response.height === '0') {
response.height = request.height;
}
}

const bid = {
bidderCode: BIDDER_CODE,
width: response.width,
height: response.height,
requestId: response.requestId,
ttl: response.ttl || 3600,
creativeId: response.creativeId,
cpm: response.cpm,
netRevenue: response.netRevenue,
currency: response.currency,
meta: response.meta || { advertiserDomains: ['retail-spot.io'] },
mediaType: response.mediaType
};

// retreive video response if present
if (response.mediaType === 'video') {
bid.vastXml = window.atob(response.vastXml);
} else {
bid.ad = response.ad;
}
if (response.adId) {
bid.adId = response.adId;
}
if (response.dealId) {
bid.dealId = response.dealId;
}

return bid;
}

registerBidder(spec);
Loading

0 comments on commit ca2f974

Please sign in to comment.