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
71 changes: 67 additions & 4 deletions modules/dfpAdServerVideo.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@

import { registerVideoSupport } from '../src/adServerManager';
import { getWinningBids } from '../src/targeting';
import { formatQS, format as buildUrl } from '../src/url';
import { parseSizesInput } from '../src/utils';
import { formatQS, format as buildUrl, parse } from '../src/url';
import { deepAccess, isEmpty, logError, parseSizesInput } from '../src/utils';
import { config } from '../src/config';

/**
* @typedef {Object} DfpVideoParams
Expand All @@ -31,8 +32,9 @@ import { parseSizesInput } from '../src/utils';
* @param [Object] bid The bid which should be considered alongside the rest of the adserver's demand.
* If this isn't defined, then we'll use the winning bid for the adUnit.
*
* @param {DfpVideoParams} params Query params which should be set on the DFP request.
* @param {DfpVideoParams} [params] Query params which should be set on the DFP request.
* These will override this module's defaults whenever they conflict.
* @param {string} [url] video adserver url
*/

/** Safe defaults which work on pretty much all video calls. */
Expand All @@ -55,9 +57,26 @@ const defaultParamConstants = {
* demand in DFP.
*/
export default function buildDfpVideoUrl(options) {
if (!options.params && !options.url) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like the JSDoc types need some updating for this. The DfpVideoOptions still says that params is required, and doesn't mention url at all.

logError(`A params object or a url is required to use pbjs.adServers.dfp.buildVideoUrl`);
return;
}

const adUnit = options.adUnit;
const bid = options.bid || getWinningBids(adUnit.code)[0];

let urlComponents = {};

if (options.url) {
// when both `url` and `params` are given, parsed url will be overwriten
// with any matching param components
urlComponents = parse(options.url);

if (isEmpty(options.params)) {
return buildUrlFromAdserverUrlComponents(urlComponents, bid);
}
}

const derivedParams = {
correlator: Date.now(),
sz: parseSizesInput(adUnit.sizes).join('|'),
Expand All @@ -73,9 +92,14 @@ export default function buildDfpVideoUrl(options) {

const queryParams = Object.assign({},
defaultParamConstants,
urlComponents.search,
derivedParams,
options.params,
{ cust_params: encodeURIComponent(formatQS(customParams)) });
{ cust_params: encodeURIComponent(formatQS(customParams)) }
);

const descriptionUrl = getDescriptionUrl(bid, options, 'params');
if (descriptionUrl) { queryParams.description_url = descriptionUrl; }

return buildUrl({
protocol: 'https',
Expand All @@ -85,6 +109,45 @@ export default function buildDfpVideoUrl(options) {
});
}

/**
* Builds a video url from a base dfp video url and a winning bid, appending
* Prebid-specific key-values.
* @param {Object} components base video adserver url parsed into components object
* @param {AdapterBidResponse} bid winning bid object to append parameters from
* @return {string} video url
*/
function buildUrlFromAdserverUrlComponents(components, bid) {
const descriptionUrl = getDescriptionUrl(bid, components, 'search');
if (descriptionUrl) { components.search.description_url = descriptionUrl; }

const adserverTargeting = (bid && bid.adserverTargeting) || {};
const customParams = Object.assign({},
adserverTargeting,
);
components.search.cust_params = encodeURIComponent(formatQS(customParams));

return buildUrl(components);
}

/**
* Returns the encoded vast url if it exists on a bid object, only if prebid-cache
* is disabled, and description_url is not already set on a given input
* @param {AdapterBidResponse} bid object to check for vast url
* @param {Object} components the object to check that description_url is NOT set on
* @param {string} prop the property of components that would contain description_url
* @return {string | undefined} The encoded vast url if it exists, or undefined
*/
function getDescriptionUrl(bid, components, prop) {
if (config.getConfig('usePrebidCache')) { return; }

if (!deepAccess(components, `${prop}.description_url`)) {
const vastUrl = bid && bid.vastUrl;
if (vastUrl) { return encodeURIComponent(vastUrl); }
} else {
logError(`input cannnot contain description_url`);
}
}

registerVideoSupport('dfp', {
buildVideoUrl: buildDfpVideoUrl
});
2 changes: 1 addition & 1 deletion src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ exports.isNumber = function(object) {
*/
exports.isEmpty = function (object) {
if (!object) return true;
if (this.isArray(object) || this.isStr(object)) {
if (exports.isArray(object) || exports.isStr(object)) {
return !(object.length > 0);
}

Expand Down
12 changes: 11 additions & 1 deletion src/video.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { videoAdapters } from './adaptermanager';
import { getBidRequest, deepAccess } from './utils';
import { getBidRequest, deepAccess, logError } from './utils';
import { config } from '../src/config';

const VIDEO_MEDIA_TYPE = 'video';
const OUTSTREAM = 'outstream';
Expand Down Expand Up @@ -32,6 +33,15 @@ export function isValidVideoBid(bid) {
// if context not defined assume default 'instream' for video bids
// instream bids require a vast url or vast xml content
if (!bidRequest || (videoMediaType && context !== OUTSTREAM)) {
// xml-only video bids require prebid-cache to be enabled
if (!config.getConfig('usePrebidCache') && bid.vastXml && !bid.vastUrl) {
Copy link
Contributor

Choose a reason for hiding this comment

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

very good idea. This makes the cache & no-cache workflows much more smooth... I'm a little embarrassed I never thought of it.

logError(`
This bid contains only vastXml and will not work when prebid-cache is disabled.
Try enabling prebid-cache with pbjs.setConfig({ usePrebidCache: true });
`);
return false;
}

return !!(bid.vastUrl || bid.vastXml);
}

Expand Down
58 changes: 58 additions & 0 deletions test/spec/modules/dfpAdServerVideo_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import parse from 'url-parse';
import buildDfpVideoUrl from 'modules/dfpAdServerVideo';
import { parseQS } from 'src/url';
import adUnit from 'test/fixtures/video/adUnit';
import { newConfig } from 'src/config';

const bid = {
videoCacheKey: 'abc',
Expand Down Expand Up @@ -36,6 +37,43 @@ describe('The DFP video support module', () => {
expect(queryParams).to.have.property('url');
});

it('can take an adserver url as a parameter', () => {
const bidCopy = Object.assign({ }, bid);
bidCopy.vastUrl = 'vastUrl.example';

const url = parse(buildDfpVideoUrl({
adUnit: adUnit,
bid: bidCopy,
url: 'https://video.adserver.example/',
}));

expect(url.host).to.equal('video.adserver.example');

const queryObject = parseQS(url.query);
expect(queryObject.description_url).to.equal('vastUrl.example');
});

it('requires a params object or url', () => {
const url = buildDfpVideoUrl({
adUnit: adUnit,
bid: bid,
});

expect(url).to.be.undefined;
});

it('overwrites url params when both url and params object are given', () => {
const url = parse(buildDfpVideoUrl({
adUnit: adUnit,
bid: bid,
url: 'https://video.adserver.example/ads?sz=640x480&iu=/123/aduniturl&impl=s',
params: { iu: 'my/adUnit' }
}));

const queryObject = parseQS(url.query);
expect(queryObject.iu).to.equal('my/adUnit');
});

it('should override param defaults with user-provided ones', () => {
const url = parse(buildDfpVideoUrl({
adUnit: adUnit,
Expand Down Expand Up @@ -92,6 +130,26 @@ describe('The DFP video support module', () => {
expect(customParams).to.have.property('my_targeting', 'foo');
});

it('should not overwrite an existing description_url for object input and cache disabled', () => {
const config = newConfig();
config.setConfig({ usePrebidCache: true });

const bidCopy = Object.assign({}, bid);
bidCopy.vastUrl = 'vastUrl.example';

const url = parse(buildDfpVideoUrl({
adUnit: adUnit,
bid: bidCopy,
params: {
iu: 'my/adUnit',
description_url: 'descriptionurl.example'
}
}));

const queryObject = parseQS(url.query);
expect(queryObject.description_url).to.equal('descriptionurl.example');
});

it('should work with nobid responses', () => {
const url = buildDfpVideoUrl({
adUnit: adUnit,
Expand Down
17 changes: 16 additions & 1 deletion test/spec/video_spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { isValidVideoBid } from 'src/video';
const utils = require('src/utils');
import { newConfig } from 'src/config';
import * as utils from 'src/utils';

describe('video.js', () => {
afterEach(() => {
Expand Down Expand Up @@ -34,6 +35,20 @@ describe('video.js', () => {
expect(valid).to.be(false);
});

it('catches invalid bids when prebid-cache is disabled', () => {
sinon.stub(utils, 'getBidRequest', () => ({
bidder: 'vastOnlyVideoBidder',
mediaTypes: { video: {} },
}));

const config = newConfig();
config.setConfig({ usePrebidCache: false });

const valid = isValidVideoBid({ vastXml: '<xml>vast</xml>' });

expect(valid).to.be(false);
});

it('validates valid outstream bids', () => {
sinon.stub(utils, 'getBidRequest', () => ({
bidder: 'appnexusAst',
Expand Down