Skip to content
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
128 changes: 128 additions & 0 deletions modules/rumbleBidAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { registerBidder } from "../src/adapters/bidderFactory.js";
import { BANNER, VIDEO } from "../src/mediaTypes.js";
import { config } from "../src/config.js";
import { ortbConverter } from "../libraries/ortbConverter/converter.js";
import {
deepSetValue,
deepAccess,
getBidIdParameter,
logError,
logWarn,
triggerPixel,
replaceAuctionPrice
} from "../src/utils.js";

const BIDDER_CODE = 'rumble';
const ENDPOINT = 'https://a.ads.rmbl.ws/v1/sites/:id/ortb';
const VERSION = '1.0.0';

function fillParameters(bid) {
const global = config.getConfig('rumble') || {};

bid.params = bid.params || {};

[
'publisherId',
'siteId',
'test',
].forEach(function(k) {
if (bid.params[k]) {
return;
}

if (global[k]) {
bid.params[k] = global[k];
}
})

return bid.params;
}

export const converter = ortbConverter({
context: {
netRevenue: true,
ttl: 60,
currency: "USD"
},
request(buildRequest, imps, bidderRequest, context) {
const request = buildRequest(imps, bidderRequest, context);
const params = fillParameters(bidderRequest?.bids[0])

if (params?.test) {
deepSetValue(request, 'test', 1)
}

deepSetValue(request, 'ext.adapter', {
version: VERSION,
name: 'prebidjs'
})

return request;
}
})

export const spec = {
code: BIDDER_CODE,
supportedMediaTypes: [BANNER, VIDEO],

isBidRequestValid: function (bid) {
fillParameters(bid)

if (bid && typeof bid.params !== 'object') {
logError(BIDDER_CODE + ': params is not defined or is incorrect in the bidder settings.');
return false;
}

const required = ['publisherId', 'siteId'];

for (let i = 0; i < required.length; i++) {
if (!getBidIdParameter(required[i], bid.params)) {
logError(BIDDER_CODE + `: ${required[i]} must be set as a bidder parameter`);
return false;
}
}

const banner = deepAccess(bid, `mediaTypes.banner`);
const video = deepAccess(bid, `mediaTypes.video`);

if (!banner && !video) {
logWarn(BIDDER_CODE + ': either banner or video mediaType must be provided')
return false;
}

return true;
},
buildRequests: function(bidRequests, bidderRequest) {
const publisherId = bidRequests[0].params.publisherId;
const siteId = bidRequests[0].params.siteId;
const zoneId = bidRequests[0].params.zoneId;
let endpoint = ENDPOINT.replace(':id', siteId) + "?pid=" + publisherId;

if (zoneId) {
endpoint += "&a=" + zoneId;
}

return bidRequests.map(bid => {
return {
url: endpoint,
method: 'POST',
data: converter.toORTB({bidRequests: [bid], bidderRequest}),
bidRequest: bid,
};
})
},
interpretResponse(response, request) {
return converter.fromORTB({response: response.body, request: request.data}).bids;
},
onBidWon: function(bid) {
if (bid.burl) {
triggerPixel(replaceAuctionPrice(bid.burl, bid.originalCpm || bid.cpm));
}

if (bid.nurl) {
triggerPixel(replaceAuctionPrice(bid.nurl, bid.originalCpm || bid.cpm));
}
},
};

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

Module Name: Rumble Bidder Adapter
Module Type: Bidder Adapter
Maintainer: adtech@rumble.com

## Description

Connects to Rumble Advertising Center (`RAC` for short) for bids.

## Configuration

### Parameters
Rumble requires multiple parameters. These parameters may be set globally or per each ad unit.

| Parameter | Global | AdUnit | Description |
|-------------|--------|--------|----------------------------------------------------|
| publisherId | x | x | Your RAC account publisher ID |
| siteId | x | x | The site ID you want to send requests |
| zoneId | | x | An optional zone ID that you want to send requests |
| test | x | x | An optional boolean flag for sending test requests |

#### Global Configuration

The global configuration is used to set parameters across all ad units instead of individually.

```javascript
pbjs.setConfig({
rumble: {
publisherId: 1,
siteId: 2,
}
})
```

#### Ad-unit configuration

All global configuration may be overridden by ad-unit configuration in addition to adding ad-unit only parameters.

```javascript
let adUnits = [
{
code: 'test-div',
mediaTypes: {
banner: {
sizes: [[300, 250]],
}
},
bids: [
{
bidder: "rumble",
params: {
publisherId: 1,
siteId: 1,
zoneId: 1, // optional
}
}
]
}
];
```

## Test Parameters


### Sample Display Ad Unit
```javascript
let adUnits = [
{
code: 'test-div',
mediaTypes: {
banner: {
sizes: [[300, 250]],
}
},
bids: [
{
bidder: "rumble",
params: {
publisherId: 1,
siteId: 1,
zoneId: 1, // optional
test: true // only while testing
}
}
]
}
]
```

### Sample Video Ad Unit
```javascript
let adUnits = [
{
code: 'test-div',
mediaTypes: {
video: {
mimes: ['video/mp4'],
}
},
bids: [
{
bidder: "rumble",
params: {
publisherId: 1,
siteId: 1,
zoneId: 1, // optional
test: true // only while testing
}
}
]
}
]
```
125 changes: 125 additions & 0 deletions test/spec/modules/rumbleBidAdapter_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import {spec, converter} from 'modules/rumbleBidAdapter.js';
import { config } from '../../../src/config.js';
import {BANNER} from "../../../src/mediaTypes.js";
import {deepClone, getUniqueIdentifierStr} from "../../../src/utils.js";
import {expect} from "chai";

const bidder = 'rumble';

describe('RumbleBidAdapter', function() {
describe('isBidRequestValid', function() {
const bidId = getUniqueIdentifierStr();
const bidderRequestId = getUniqueIdentifierStr();

function newBid() {
return {
bidder,
bidId,
bidderRequestId,
params: {
publisherId: '123',
siteId: '321',
},
mediaTypes: {
[BANNER]: {
sizes: [[300, 250]]
}
},
}
}

it('should return true when all required parameters exist', function() {
expect(spec.isBidRequestValid(newBid())).to.equal(true);
});

it('should return false when publisherId is not present', function() {
let bid = newBid();
delete bid.params.publisherId;
expect(spec.isBidRequestValid(bid)).to.equal(false);
});

it('should return false when siteId is not present', function() {
let bid = newBid();
delete bid.params.siteId;
expect(spec.isBidRequestValid(bid)).to.equal(false);
});

it('should return false if mediaTypes.banner or video is not present', function () {
let bid = newBid();
delete bid.mediaTypes
expect(spec.isBidRequestValid(bid)).to.equal(false);
});

it('should return true when global configuration is present', function() {
let bid = newBid();
delete bid.params.publisherId;
delete bid.params.siteId;

config.mergeConfig({
rumble: {
publisherId: 1,
siteId: 1,
test: true,
}
});

expect(spec.isBidRequestValid(bid)).to.equal(true);
});
});

describe('buildRequests', function() {
let bidRequests = [{
bidder: 'rumble',
params: {
publisherId: 1,
siteId: 2,
zoneId: 3,
},
mediaTypes: {
banner: {
sizes: [[300, 250]]
}
},
sizes: [[300, 250]],
bidId: getUniqueIdentifierStr(),
bidderRequestId: getUniqueIdentifierStr(),
auctionId: getUniqueIdentifierStr(),
src: 'client',
bidRequestsCount: 1
}];

let bidderRequest = {
bidderCode: 'rumble',
auctionId: getUniqueIdentifierStr(),
refererInfo: {
domain: 'localhost',
page: 'http://localhost/integrationExamples/gpt/hello_world.html?pbjs_debug=true',
},
ortb2: {
site: {
publisher: {
name: 'rumble'
}
}
}
};

function createRequests(bidRequests, bidderRequest) {
let cbr = deepClone(bidderRequest);
cbr.bids = bidRequests;
return spec.buildRequests(bidRequests, cbr);
}

it('should validate request', function() {
let requests = createRequests(bidRequests, bidderRequest);

expect(requests).to.have.lengthOf(bidRequests.length);

requests.forEach(function(request, idx) {
expect(request.method).to.equal('POST');
expect(request.url).to.equal('https://a.ads.rmbl.ws/v1/sites/2/ortb?pid=1&a=3');
expect(request.bidRequest).to.equal(bidRequests[idx]);
});
});
});
});
Loading