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

Freedom Ad Network Bid Adapter: initial release #12153

Merged
merged 15 commits into from
Aug 21, 2024
Merged
227 changes: 227 additions & 0 deletions modules/fanAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
import * as utils from '../src/utils.js';
import MD5 from 'crypto-js/md5.js';
import { ajax } from '../src/ajax.js';
import { BANNER, NATIVE } from '../src/mediaTypes.js';
import { config } from '../src/config.js';
import { registerBidder } from '../src/adapters/bidderFactory.js';
const BIDDER_CODE = 'freedomadnetwork';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you may want a short code alias

const BASE_URL = 'https://srv.freedomadnetwork.com';

/**
* Get user id from bid request. if no user id module used, return a new uuid.
*
* @param {BidRequest} bidRequest
* @returns {String} userId
*/
function getUserId(bidRequest) {
return generateUserId();
}

/**
* Get browser language
*
* @returns {String} language
*/
function getLanguage() {
const lang = (navigator.languages && navigator.languages[0]) ||
navigator.language || navigator.userLanguage;
return lang ? lang.split('-')[0] : 'en';
}

/**
* Get device info
*
* @returns {Object}
*/
function getDevice() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all of this should come off the request object

const device = config.getConfig('device') || {};

device.w = device.w || window.screen.width;
device.h = device.h || window.screen.height;
device.ua = device.ua || navigator.userAgent;
device.language = device.language || getLanguage();
device.dnt = typeof device.dnt === 'number'
? device.dnt : (utils.getDNT() ? 1 : 0);

return device;
}

/**
* Build OpenRTB request from bidRequest and bidderRequest
*
* @param {BidRequest} bidRequest
* @param {BidderRequest} bidderRequest
* @returns {Request}
*/
function buildBidRequest(bid, bidderRequest) {
const userId = getUserId(bid);

const payload = {
id: bid.bidId,
tmax: bidderRequest.timeout,
placements: [bid.params.placementId],
test: config.getConfig('debug') ? 1 : 0,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is on the request

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based myself off of the https://github.com/prebid/Prebid.js/blob/master/modules/operaadsBidAdapter.js#L246 adapter. Is there a newer one to use as reference?

device: getDevice(),
at: 2,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this isnt true

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean? Should be a constant?

user: {
coppa: config.getConfig('coppa') ? 1 : 0,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is also on the request

}
}

const gdprConsent = utils.deepAccess(bidderRequest, 'gdprConsent');
if (!!gdprConsent && gdprConsent.gdprApplies) {
payload.user.gdpr = 1;
payload.user.consent = gdprConsent.consentString;
}

const uspConsent = utils.deepAccess(bidderRequest, 'uspConsent');
if (uspConsent) {
payload.user.usp = uspConsent;
}

return {
method: 'POST',
url: BASE_URL + '/pb/req',
data: JSON.stringify(payload),
options: {
contentType: 'application/json',
withCredentials: false,
customHeaders: {
'Accept-Language': 'en;q=10',
'Authorization': 'Bearer ' + userId
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is odd

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, I cannot change this. Server side requires this interface.

Copy link
Collaborator

@patmmccann patmmccann Aug 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why? just fix the server side?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no control over this part of the service.

},
},
originalBidRequest: bid
}
}

/**
* Generate stable user id
*
* @returns {String} userId
*/
function generateUserId() {
var hash = MD5(navigator.userAgent).toString();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why perform an expensive calculation on this instead of just sending 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.

Server side expects an md5.


return hash;
}

export const spec = {
code: BIDDER_CODE,
isBidRequestValid: function(bid) {
if (!bid) {
utils.logWarn(BIDDER_CODE, 'Invalid bid', bid);

return false;
}

if (!bid.params) {
utils.logWarn(BIDDER_CODE, 'bid.params is required');

return false;
}

if (!bid.params.placementId) {
utils.logWarn(BIDDER_CODE, 'bid.params.placementId is required');

return false;
}

var banner = utils.deepAccess(bid, 'mediaTypes.banner');
if (banner === undefined) {
return false;
}

return true;
},

buildRequests: function(validBidRequests, bidderRequest) {
return validBidRequests.map(bid => buildBidRequest(bid, bidderRequest));
},

/**
* Unpack the response from the server into a list of bids.
*
* @param {ServerResponse} serverResponse A successful response from the server.
* @return {Bid[]} An array of bids which were nested inside the server.
*/
interpretResponse: function (serverResponse, bidRequest) {
const serverBody = serverResponse.body;
let bidResponses = [];

if (!serverBody) {
return bidResponses;
}

serverBody.forEach((response) => {
const bidResponse = {
requestId: response.id,
bidid: response.bidid,
impid: response.impid,
userId: response.userId,
cpm: response.cpm,
currency: response.currency,
width: response.width,
height: response.height,
ad: response.payload,
ttl: response.ttl,
creativeId: response.crid,
netRevenue: response.netRevenue,
trackers: response.trackers,
meta: {
mediaType: response.mediaType,
advertiserDomains: response.domains,
}
};

bidResponses.push(bidResponse);
});

return bidResponses;
},

/**
* Register bidder specific code, which will execute if a bid from this bidder won the auction
*
* @param {Bid} bid The bid that won the auction
*/
onBidWon: function (bid) {
if (!bid) {
return;
}

const payload = {
id: bid.bidid,
impid: bid.impid,
t: bid.cpm,
}

ajax(BASE_URL + '/pb/imp', null, JSON.stringify(payload), {
method: 'POST',
customHeaders: {
'Accept-Language': 'en;q=10',
'Authorization': 'Bearer ' + bid.userId
},
});

if (bid.trackers && bid.trackers.length > 0) {
for (var i = 0; i < bid.trackers.length; i++) {
if (bid.trackers[i].type == 0) {
utils.triggerPixel(bid.trackers[i].url);
}
}
}
},
onSetTargeting: function(bid) {},
onBidderError: function(error) {
utils.logError(`${BIDDER_CODE} bidder error`, error);
},
getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) {
const syncs = [];
return syncs;
},
onTimeout: function(timeoutData) {},
supportedMediaTypes: [BANNER, NATIVE]
}

registerBidder(spec);
40 changes: 40 additions & 0 deletions modules/fanAdapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Freedom Ad Network Bidder Adapter

# Overview

```
Module Name: Freedom Ad Network Bidder Adapter
Module Type: Bidder Adapter
Maintainer: [email protected]
```

## Description

Module that connects to FAN's demand sources.

## Bid Parameters

| Name | Scope | Type | Description | Example |
|---------------|----------|--------------------|-----------------------------------------|-------------------------------------------------|
| `placementId` | required | String | The Placement Id provided by FAN. | `e6203f1e-bd6d-4f42-9895-d1a19cdb83c8` |

## Example

### Banner Ads

```javascript
var adUnits = [{
code: 'banner-ad-div',
mediaTypes: {
banner: {
sizes: [[300, 250]]
}
},
bids: [{
bidder: 'freedomadnetwork',
params: {
placementId: 'e6203f1e-bd6d-4f42-9895-d1a19cdb83c8'
}
}]
}];
```
Loading