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

FeedAd bidder adapter #3891

Merged
merged 24 commits into from
Jun 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
6813398
added file scaffold
couchcrew-thomas May 2, 2019
030a709
added isBidRequestValid implementation
couchcrew-thomas May 7, 2019
1ac46eb
added local prototype of ad integration
couchcrew-thomas May 8, 2019
fdcee20
added implementation for placement ID validation
couchcrew-thomas May 20, 2019
1938094
fixed video context filter
couchcrew-thomas May 20, 2019
478b00d
applied lint to feedad bid adapter
couchcrew-thomas May 20, 2019
494258e
added unit test for bid request validation
couchcrew-thomas May 20, 2019
c7111e8
added buildRequest unit test
couchcrew-thomas May 20, 2019
e57779c
added unit tests for timeout and bid won callbacks
couchcrew-thomas May 22, 2019
0af865c
updated bid request to FeedAd API
couchcrew-thomas Jun 3, 2019
4d21899
added parsing of feedad api bid response
couchcrew-thomas Jun 3, 2019
3185358
added transmisison of tracking events to FeedAd Api
couchcrew-thomas Jun 5, 2019
d7673e4
code cleanup
couchcrew-thomas Jun 7, 2019
79a3cbc
updated feedad unit tests for buildRequest method
couchcrew-thomas Jun 7, 2019
d446300
added unit tests for event tracking implementation
couchcrew-thomas Jun 7, 2019
766f23c
added unit test for interpretResponse method
couchcrew-thomas Jun 7, 2019
2d469c0
added adapter documentation
couchcrew-thomas Jun 7, 2019
138d1ff
added dedicated feedad example page
couchcrew-thomas Jun 7, 2019
e4c7214
updated feedad adapter to use live system
couchcrew-thomas Jun 7, 2019
0eabd34
updated FeedAd adapter placement ID regex
couchcrew-thomas Jun 10, 2019
1ff7caa
removed groups from FeedAd adapter placement ID regex
couchcrew-thomas Jun 11, 2019
748590e
removed dedicated feedad example page
couchcrew-thomas Jun 14, 2019
98e422a
updated imports in FeedAd adapter file to use relative paths
couchcrew-thomas Jun 14, 2019
f2836f3
updated FeedAd adapter unit test to use sinon.useFakeXMLHttpRequest()
couchcrew-thomas Jun 14, 2019
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
290 changes: 290 additions & 0 deletions modules/feedadBidAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
import * as utils from '../src/utils';
import {registerBidder} from '../src/adapters/bidderFactory';
import {BANNER, VIDEO} from '../src/mediaTypes';
import {ajax} from '../src/ajax';

/**
* Version of the FeedAd bid adapter
* @type {string}
*/
const VERSION = '1.0.0';

/**
* @typedef {object} FeedAdApiBidRequest
* @inner
*
* @property {number} ad_type
* @property {string} client_token
* @property {string} placement_id
* @property {string} sdk_version
* @property {boolean} app_hybrid
*
* @property {string} [app_bundle_id]
* @property {string} [app_name]
* @property {object} [custom_params]
* @property {number} [connectivity]
* @property {string} [device_adid]
* @property {string} [device_platform]
*/

/**
* @typedef {object} FeedAdApiBidResponse
* @inner
*
* @property {string} ad - Ad HTML payload
* @property {number} cpm - number / float
* @property {string} creativeId - ID of creative for tracking
* @property {string} currency - 3-letter ISO 4217 currency-code
* @property {number} height - Height of creative returned in [].ad
* @property {boolean} netRevenue - Is the CPM net (true) or gross (false)?
* @property {string} requestId - bids[].bidId
* @property {number} ttl - Time to live for this ad
* @property {number} width - Width of creative returned in [].ad
*/

/**
* @typedef {object} FeedAdApiTrackingParams
* @inner
*
* @property app_hybrid {boolean}
* @property client_token {string}
* @property klass {'prebid_bidWon'|'prebid_bidTimeout'}
* @property placement_id {string}
* @property prebid_auction_id {string}
* @property prebid_bid_id {string}
* @property prebid_transaction_id {string}
* @property referer {string}
* @property sdk_version {string}
* @property [app_bundle_id] {string}
* @property [app_name] {string}
* @property [device_adid] {string}
* @property [device_platform] {1|2|3} 1 - Android | 2 - iOS | 3 - Windows
*/

/**
* Bidder network identity code
* @type {string}
*/
const BIDDER_CODE = 'feedad';

/**
* The media types supported by FeedAd
* @type {MediaType[]}
*/
const MEDIA_TYPES = [VIDEO, BANNER];

/**
* Tag for logging
* @type {string}
*/
const TAG = '[FeedAd]';

/**
* Pattern for valid placement IDs
* @type {RegExp}
*/
const PLACEMENT_ID_PATTERN = /^[a-z0-9][a-z0-9_-]+[a-z0-9]$/;

const API_ENDPOINT = 'https://api.feedad.com';
const API_PATH_BID_REQUEST = '/1/prebid/web/bids';
const API_PATH_TRACK_REQUEST = '/1/prebid/web/events';

/**
* Stores temporary auction metadata
* @type {Object.<string, {referer: string, transactionId: string}>}
*/
const BID_METADATA = {};

/**
* Checks if the bid is compatible with FeedAd.
*
* @param {BidRequest} bid - the bid to check
* @return {boolean} true if the bid is valid
*/
function isBidRequestValid(bid) {
const clientToken = utils.deepAccess(bid, 'params.clientToken');
if (!clientToken || !isValidClientToken(clientToken)) {
utils.logWarn(TAG, "missing or invalid parameter 'clientToken'. found value:", clientToken);
return false;
}

const placementId = utils.deepAccess(bid, 'params.placementId');
if (!placementId || !isValidPlacementId(placementId)) {
utils.logWarn(TAG, "missing or invalid parameter 'placementId'. found value:", placementId);
return false;
}

return true;
}

/**
* Checks if a client token is valid
* @param {string} clientToken - the client token
* @return {boolean} true if the token is valid
*/
function isValidClientToken(clientToken) {
return typeof clientToken === 'string' && clientToken.length > 0;
}

/**
* Checks if the given placement id is of a correct format.
* Valid IDs are words of lowercase letters from a to z and numbers from 0 to 9.
* The words can be separated by hyphens or underscores.
* Multiple separators must not follow each other.
* The whole placement ID must not be larger than 256 characters.
*
* @param placementId - the placement id to verify
* @returns if the placement ID is valid.
*/
function isValidPlacementId(placementId) {
return typeof placementId === 'string' &&
placementId.length > 0 &&
placementId.length <= 256 &&
PLACEMENT_ID_PATTERN.test(placementId);
}

/**
* Checks if the given media types contain unsupported settings
* @param {MediaTypes} mediaTypes - the media types to check
* @return {MediaTypes} the unsupported settings, empty when all types are supported
*/
function filterSupportedMediaTypes(mediaTypes) {
return {
banner: mediaTypes.banner,
video: mediaTypes.video && mediaTypes.video.context === 'outstream' ? mediaTypes.video : undefined,
native: undefined
};
}

/**
* Checks if the given media types are empty
* @param {MediaTypes} mediaTypes - the types to check
* @return {boolean} true if the types are empty
*/
function isMediaTypesEmpty(mediaTypes) {
return Object.keys(mediaTypes).every(type => mediaTypes[type] === undefined);
}

/**
* Creates the bid request params the api expects from the prebid bid request
* @param {BidRequest} request - the validated prebid bid request
* @return {FeedAdApiBidRequest}
*/
function createApiBidRParams(request) {
return {
ad_type: 0,
client_token: request.params.clientToken,
placement_id: request.params.placementId,
sdk_version: `prebid_${VERSION}`,
app_hybrid: false,
};
}

/**
* Builds the bid request to the FeedAd Server
* @param {BidRequest[]} validBidRequests - all validated bid requests
* @param {object} bidderRequest - meta information
* @return {ServerRequest|ServerRequest[]}
*/
function buildRequests(validBidRequests, bidderRequest) {
if (!bidderRequest) {
return [];
}
let acceptableRequests = validBidRequests.filter(request => !isMediaTypesEmpty(filterSupportedMediaTypes(request.mediaTypes)));
if (acceptableRequests.length === 0) {
return [];
}
let data = Object.assign({}, bidderRequest, {
bids: acceptableRequests.map(req => {
req.params = createApiBidRParams(req);
return req;
})
});
data.bids.forEach(bid => BID_METADATA[bid.bidId] = {
referer: data.refererInfo.referer,
transactionId: bid.transactionId
});
return {
method: 'POST',
url: `${API_ENDPOINT}${API_PATH_BID_REQUEST}`,
data,
options: {
contentType: 'application/json'
}
};
}

/**
* Adapts the FeedAd server response to Prebid format
* @param {ServerResponse} serverResponse - the FeedAd server response
* @param {BidRequest} request - the initial bid request
* @returns {Bid[]} the FeedAd bids
*/
function interpretResponse(serverResponse, request) {
/**
* @type FeedAdApiBidResponse[]
*/
return typeof serverResponse.body === 'string' ? JSON.parse(serverResponse.body) : serverResponse.body;
}

/**
* Creates the parameters for the FeedAd tracking call
* @param {object} data - prebid data
* @param {'prebid_bidWon'|'prebid_bidTimeout'} klass - type of tracking call
* @return {FeedAdApiTrackingParams|null}
*/
function createTrackingParams(data, klass) {
const bidId = data.bidId || data.requestId;
if (!BID_METADATA.hasOwnProperty(bidId)) {
return null;
}
const {referer, transactionId} = BID_METADATA[bidId];
delete BID_METADATA[bidId];
return {
app_hybrid: false,
client_token: data.params[0].clientToken,
placement_id: data.params[0].placementId,
klass,
prebid_auction_id: data.auctionId,
prebid_bid_id: bidId,
prebid_transaction_id: transactionId,
referer,
sdk_version: VERSION
};
}

/**
* Creates a tracking handler for the given event type
* @param klass - the event type
* @return {Function} the tracking handler function
*/
function trackingHandlerFactory(klass) {
return (data) => {
if (!data) {
return;
}
let params = createTrackingParams(data, klass);
if (params) {
ajax(`${API_ENDPOINT}${API_PATH_TRACK_REQUEST}`, null, JSON.stringify(params), {
withCredentials: true,
method: 'POST',
contentType: 'application/json'
});
}
}
}

/**
* @type {BidderSpec}
*/
export const spec = {
code: BIDDER_CODE,
supportedMediaTypes: MEDIA_TYPES,
isBidRequestValid,
buildRequests,
interpretResponse,
onTimeout: trackingHandlerFactory('prebid_bidTimeout'),
onBidWon: trackingHandlerFactory('prebid_bidWon')
};

registerBidder(spec);
44 changes: 44 additions & 0 deletions modules/feedadBidAdapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Overview

```
Module Name: FeedAd Adapter
Module Type: Bidder Adapter
Maintainer: mail@feedad.com
```

# Description

Prebid.JS adapter that connects to the FeedAd demand sources.

# Test Parameters
```
var adUnits = [
{
code: 'test-div',
mediaTypes: {
banner: { // supports all banner sizes
sizes: [[300, 250]],
},
video: { // supports only outstream video
context: 'outstream'
}
},
bids: [
{
bidder: "feedad",
params: {
clientToken: 'your-client-token' // see below for more info
placementId: 'your-placement-id' // see below for more info
}
}
]
}
];
```

# Required Parameters

| Parameter | Description |
| --------- | ----------- |
| `clientToken` | Your FeedAd web client token. You can view your client token inside the FeedAd admin panel. |
| `placementId` | You can choose placement IDs yourself. A placement ID should be named after the ad position inside your product. For example, if you want to display an ad inside a list of news articles, you could name it "ad-news-overview".<br> A placement ID may consist of lowercase `a-z`, `0-9`, `-` and `_`. You do not have to manually create the placement IDs before using them. Just specify them within the code, and they will appear in the FeedAd admin panel after the first request. <br> [Learn more](/concept/feed_ad/index.html) about Placement IDs and how they are grouped to play the same Creative. |
Loading