Skip to content
Open
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
75 changes: 67 additions & 8 deletions modules/cwireBidAdapter.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { registerBidder } from "../src/adapters/bidderFactory.js";
import { getStorageManager } from "../src/storageManager.js";
import { BANNER } from "../src/mediaTypes.js";
import { BANNER, VIDEO } from "../src/mediaTypes.js";
import {
getParameterByName,
isNumber,
Expand All @@ -22,6 +22,7 @@ import { getAdUnitElement } from '../src/utils/adUnits.js';
// ------------------------------------
const BIDDER_CODE = "cwire";
const CWID_KEY = "cw_cwid";
const VAST_XML_REGEX = /^\s*(?:<\?xml[^>]*>\s*)?<VAST[\s>]/i;

export const BID_ENDPOINT = "https://prebid.cwi.re/v1/bid";
export const EVENT_ENDPOINT = "https://prebid.cwi.re/v1/event";
Expand Down Expand Up @@ -169,10 +170,68 @@ function getCwExtension() {
};
}

function getBidRequest(responseBid, request) {
return request?.bids?.find((bid) => (
bid.bidId === responseBid.requestId ||
bid.bidId === responseBid.bidId
));
}

function isVastXml(value) {
return typeof value === "string" && VAST_XML_REGEX.test(value);
}

function isVideoResponse(responseBid, requestBid) {
const hasVast = (
responseBid.vastXml ||
responseBid.vastXML ||
responseBid.vastUrl ||
responseBid.vastURL ||
isVastXml(responseBid.html)
);
const hasVideoMediaType = (
responseBid.mediaType === VIDEO ||
responseBid.adType === VIDEO ||
responseBid.type === VIDEO
);
const requestSupportsVideo = !!requestBid?.mediaTypes?.video;

return (
hasVast ||
(requestSupportsVideo && (hasVideoMediaType || !requestBid?.mediaTypes?.banner))
);
}

function mapBidResponse(responseBid, request) {
const { html, vastXml, vastXML, vast, vastUrl, vastURL, ...rest } = responseBid;
const requestBid = getBidRequest(responseBid, request);

if (isVideoResponse(responseBid, requestBid)) {
const vastXmlResponse = vastXml || vastXML || vast || (isVastXml(html) ? html : null);
const vastUrlResponse = vastUrl || vastURL;

if (!vastXmlResponse && !vastUrlResponse) {
return null;
}

return {
...rest,
mediaType: VIDEO,
...(vastXmlResponse && { vastXml: vastXmlResponse }),
...(vastUrlResponse && { vastUrl: vastUrlResponse }),
};
}

return {
...rest,
ad: html,
};
}

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

/**
* Determines whether the given bid request is valid.
Expand Down Expand Up @@ -255,6 +314,7 @@ export const spec = {
method: "POST",
url: BID_ENDPOINT,
data: payloadString,
bids: validBidRequests,
};
},
/**
Expand All @@ -271,12 +331,11 @@ export const spec = {
}
}

// Rename `html` response property to `ad` as used by prebid.
const bids = serverResponse.body?.bids.map(({ html, ...rest }) => ({
...rest,
ad: html,
}));
return bids || [];
// Rename banner `html` to `ad`; video responses must expose VAST.
const bids = (serverResponse.body?.bids || [])
.map((bid) => mapBidResponse(bid, bidRequest))
.filter(Boolean);
return bids;
},

onBidWon: function (bid) {
Expand Down
32 changes: 32 additions & 0 deletions modules/cwireBidAdapter.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ Below, the list of C-WIRE params and where they can be set.

### adUnit configuration

#### Banner

```javascript
var adUnits = [
{
Expand Down Expand Up @@ -65,6 +67,36 @@ var adUnits = [
];
```

#### Video

```javascript
var adUnits = [
{
code: 'video_target_div_id', // REQUIRED
mediaTypes: {
video: {
context: 'instream',
playerSize: [640, 480],
mimes: ['video/mp4'],
protocols: [2, 3, 5, 6],
startdelay: 0,
placement: 1,
playbackmethod: [2],
api: [2],
linearity: 1
}
},
bids: [{
bidder: 'cwire',
params: {
domainId: 1422, // required - number
placementId: 2211521, // optional - number
}
}]
}
];
```

### URL parameters

For debugging and testing purposes url parameters can be set.
Expand Down
141 changes: 141 additions & 0 deletions test/spec/modules/cwireBidAdapter_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import sinon, { stub } from "sinon";
import { config } from "../../../src/config.js";
import * as autoplayLib from "../../../libraries/autoplayDetection/autoplay.js";
import * as adUnits from 'src/utils/adUnits';
import { BANNER, VIDEO } from "../../../src/mediaTypes.js";

describe("C-WIRE bid adapter", () => {
config.setConfig({ debug: true });
Expand Down Expand Up @@ -53,6 +54,31 @@ describe("C-WIRE bid adapter", () => {
],
},
};
const vastXml = '<VAST version="3.0"><Ad></Ad></VAST>';
const videoBidRequest = {
bidder: "cwire",
params: {
domainId: 4057,
},
adUnitCode: "video-adunit-code",
mediaTypes: {
video: {
context: "instream",
playerSize: [640, 480],
mimes: ["video/mp4"],
protocols: [2, 3, 5, 6],
startdelay: 0,
placement: 1,
playbackmethod: [2],
api: [2],
linearity: 1,
},
},
bidId: "video-bid-id",
bidderRequestId: "video-request-id",
auctionId: "video-auction-id",
transactionId: "video-transaction-id",
};

beforeEach(function () {
sandbox = sinon.createSandbox();
Expand All @@ -68,13 +94,25 @@ describe("C-WIRE bid adapter", () => {
expect(spec.buildRequests).to.exist.and.to.be.a("function");
expect(spec.interpretResponse).to.exist.and.to.be.a("function");
});

it("supports banner and video media types", function () {
expect(spec.supportedMediaTypes).to.include.members([BANNER, VIDEO]);
});
});
describe("buildRequests", function () {
it("sends bid request to ENDPOINT via POST", function () {
const request = spec.buildRequests(bidRequests, bidderRequest);
expect(request.url).to.equal(BID_ENDPOINT);
expect(request.method).to.equal("POST");
});

it("passes video mediaTypes to the bid endpoint", function () {
const request = spec.buildRequests([videoBidRequest], bidderRequest);
const payload = JSON.parse(request.data);

expect(payload.slots[0].mediaTypes.video).to.deep.equal(videoBidRequest.mediaTypes.video);
expect(request.bids[0]).to.deep.equal(videoBidRequest);
});
});
describe("buildRequests with given creative", function () {
let utilsStub;
Expand Down Expand Up @@ -318,6 +356,109 @@ describe("C-WIRE bid adapter", () => {

expect(bids[0].ad).to.exist;
});

it("keeps banner responses on the banner path even if a custom type field is present", function () {
const bidResponse = deepClone(response);
bidResponse.body.bids[0].type = VIDEO;
const bids = spec.interpretResponse(bidResponse, { bids: bidRequests });

expect(bids[0].ad).to.equal("<h1>Hello world</h1>");
expect(bids[0].mediaType).to.not.equal(VIDEO);
});

it("maps VAST XML responses to video bids", function () {
const bidResponse = {
body: {
bids: [
{
cpm: 5,
currency: "USD",
dimensions: [640, 480],
netRevenue: true,
creativeId: "video-creative",
requestId: videoBidRequest.bidId,
ttl: 360,
vastXml,
},
],
},
};
const bids = spec.interpretResponse(bidResponse, { bids: [videoBidRequest] });

expect(bids[0].mediaType).to.equal(VIDEO);
expect(bids[0].vastXml).to.equal(vastXml);
expect(bids[0].ad).to.not.exist;
});

it("maps VAST URL responses to video bids", function () {
const vastUrl = "https://prebid.cwi.re/vast/video-ad.xml";
const bidResponse = {
body: {
bids: [
{
cpm: 5,
currency: "USD",
dimensions: [640, 480],
netRevenue: true,
creativeId: "video-creative",
requestId: videoBidRequest.bidId,
ttl: 360,
vastUrl,
},
],
},
};
const bids = spec.interpretResponse(bidResponse, { bids: [videoBidRequest] });

expect(bids[0].mediaType).to.equal(VIDEO);
expect(bids[0].vastUrl).to.equal(vastUrl);
});

it("uses VAST XML from html when responding to a video-only request", function () {
const bidResponse = {
body: {
bids: [
{
html: vastXml,
cpm: 5,
currency: "USD",
dimensions: [640, 480],
netRevenue: true,
creativeId: "video-creative",
requestId: videoBidRequest.bidId,
ttl: 360,
},
],
},
};
const bids = spec.interpretResponse(bidResponse, { bids: [videoBidRequest] });

expect(bids[0].mediaType).to.equal(VIDEO);
expect(bids[0].vastXml).to.equal(vastXml);
expect(bids[0].ad).to.not.exist;
});

it("drops video responses that do not include VAST", function () {
const bidResponse = {
body: {
bids: [
{
html: "<h1>Hello world</h1>",
cpm: 5,
currency: "USD",
dimensions: [640, 480],
netRevenue: true,
creativeId: "video-creative",
requestId: videoBidRequest.bidId,
ttl: 360,
},
],
},
};
const bids = spec.interpretResponse(bidResponse, { bids: [videoBidRequest] });

expect(bids).to.be.empty;
});
});

describe("add user-syncs", function () {
Expand Down
Loading