-
Notifications
You must be signed in to change notification settings - Fork 2.1k
/
dfpAdServerVideo.js
275 lines (244 loc) · 10.3 KB
/
dfpAdServerVideo.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
/**
* This module adds [DFP support]{@link https://www.doubleclickbygoogle.com/} for Video to Prebid.
*/
import {registerVideoSupport} from '../src/adServerManager.js';
import {targeting} from '../src/targeting.js';
import {
buildUrl,
deepAccess,
formatQS,
isEmpty,
isNumber,
logError,
parseSizesInput,
parseUrl,
uniques
} from '../src/utils.js';
import {config} from '../src/config.js';
import {getHook} from '../src/hook.js';
import {auctionManager} from '../src/auctionManager.js';
import {gdprDataHandler} from '../src/adapterManager.js';
import * as events from '../src/events.js';
import {EVENTS} from '../src/constants.js';
import {getPPID} from '../src/adserver.js';
import {getRefererInfo} from '../src/refererDetection.js';
import {CLIENT_SECTIONS} from '../src/fpd/oneClient.js';
import {DEFAULT_DFP_PARAMS, DFP_ENDPOINT} from '../libraries/dfpUtils/dfpUtils.js';
/**
* @typedef {Object} DfpVideoParams
*
* This object contains the params needed to form a URL which hits the
* [DFP API]{@link https://support.google.com/dfp_premium/answer/1068325?hl=en}.
*
* All params (except iu, mentioned below) should be considered optional. This module will choose reasonable
* defaults for all of the other required params.
*
* The cust_params property, if present, must be an object. It will be merged with the rest of the
* standard Prebid targeting params (hb_adid, hb_bidder, etc).
*
* @param {string} iu This param *must* be included, in order for us to create a valid request.
* @param [string] description_url This field is required if you want Ad Exchange to bid on our ad unit...
* but otherwise optional
*/
/**
* @typedef {Object} DfpVideoOptions
*
* @param {Object} adUnit The adUnit which this bid is supposed to help fill.
* @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.
* These will override this module's defaults whenever they conflict.
* @param {string} [url] video adserver url
*/
export const dep = {
ri: getRefererInfo
}
/**
* Merge all the bid data and publisher-supplied options into a single URL, and then return it.
*
* @see [The DFP API]{@link https://support.google.com/dfp_premium/answer/1068325?hl=en#env} for details.
*
* @param {DfpVideoOptions} options Options which should be used to construct the URL.
*
* @return {string} A URL which calls DFP, letting options.bid
* (or the auction's winning bid for this adUnit, if undefined) compete alongside the rest of the
* demand in DFP.
*/
export function buildDfpVideoUrl(options) {
if (!options.params && !options.url) {
logError(`A params object or a url is required to use $$PREBID_GLOBAL$$.adServers.dfp.buildVideoUrl`);
return;
}
const adUnit = options.adUnit;
const bid = options.bid || targeting.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 = parseUrl(options.url, {noDecodeWholeURL: true});
if (isEmpty(options.params)) {
return buildUrlFromAdserverUrlComponents(urlComponents, bid, options);
}
}
const derivedParams = {
correlator: Date.now(),
sz: parseSizesInput(deepAccess(adUnit, 'mediaTypes.video.playerSize')).join('|'),
url: encodeURIComponent(location.href),
};
const urlSearchComponent = urlComponents.search;
const urlSzParam = urlSearchComponent && urlSearchComponent.sz;
if (urlSzParam) {
derivedParams.sz = urlSzParam + '|' + derivedParams.sz;
}
let encodedCustomParams = getCustParams(bid, options, urlSearchComponent && urlSearchComponent.cust_params);
const queryParams = Object.assign({},
DEFAULT_DFP_PARAMS,
urlComponents.search,
derivedParams,
options.params,
{ cust_params: encodedCustomParams }
);
const descriptionUrl = getDescriptionUrl(bid, options, 'params');
if (descriptionUrl) { queryParams.description_url = descriptionUrl; }
const gdprConsent = gdprDataHandler.getConsentData();
if (gdprConsent) {
if (typeof gdprConsent.gdprApplies === 'boolean') { queryParams.gdpr = Number(gdprConsent.gdprApplies); }
if (gdprConsent.consentString) { queryParams.gdpr_consent = gdprConsent.consentString; }
if (gdprConsent.addtlConsent) { queryParams.addtl_consent = gdprConsent.addtlConsent; }
}
if (!queryParams.ppid) {
const ppid = getPPID();
if (ppid != null) {
queryParams.ppid = ppid;
}
}
const video = options.adUnit?.mediaTypes?.video;
Object.entries({
plcmt: () => video?.plcmt,
min_ad_duration: () => isNumber(video?.minduration) ? video.minduration * 1000 : null,
max_ad_duration: () => isNumber(video?.maxduration) ? video.maxduration * 1000 : null,
vpos() {
const startdelay = video?.startdelay;
if (isNumber(startdelay)) {
if (startdelay === -2) return 'postroll';
if (startdelay === -1 || startdelay > 0) return 'midroll';
return 'preroll';
}
},
vconp: () => Array.isArray(video?.playbackmethod) && video.playbackmethod.every(m => m === 7) ? '2' : undefined,
vpa() {
// playbackmethod = 3 is play on click; 1, 2, 4, 5, 6 are autoplay
if (Array.isArray(video?.playbackmethod)) {
const click = video.playbackmethod.some(m => m === 3);
const auto = video.playbackmethod.some(m => [1, 2, 4, 5, 6].includes(m));
if (click && !auto) return 'click';
if (auto && !click) return 'auto';
}
},
vpmute() {
// playbackmethod = 2, 6 are muted; 1, 3, 4, 5 are not
if (Array.isArray(video?.playbackmethod)) {
const muted = video.playbackmethod.some(m => [2, 6].includes(m));
const talkie = video.playbackmethod.some(m => [1, 3, 4, 5].includes(m));
if (muted && !talkie) return '1';
if (talkie && !muted) return '0';
}
}
}).forEach(([param, getter]) => {
if (!queryParams.hasOwnProperty(param)) {
const val = getter();
if (val != null) {
queryParams[param] = val;
}
}
});
const fpd = auctionManager.index.getBidRequest(options.bid || {})?.ortb2 ??
auctionManager.index.getAuction(options.bid || {})?.getFPD()?.global;
function getSegments(sections, segtax) {
return sections
.flatMap(section => deepAccess(fpd, section) || [])
.filter(datum => datum.ext?.segtax === segtax)
.flatMap(datum => datum.segment?.map(seg => seg.id))
.filter(ob => ob)
.filter(uniques)
}
const signals = Object.entries({
IAB_AUDIENCE_1_1: getSegments(['user.data'], 4),
IAB_CONTENT_2_2: getSegments(CLIENT_SECTIONS.map(section => `${section}.content.data`), 6)
}).map(([taxonomy, values]) => values.length ? {taxonomy, values} : null)
.filter(ob => ob);
if (signals.length) {
queryParams.ppsj = btoa(JSON.stringify({
PublisherProvidedTaxonomySignals: signals
}))
}
return buildUrl(Object.assign({}, DFP_ENDPOINT, urlComponents, { search: queryParams }));
}
export function notifyTranslationModule(fn) {
fn.call(this, 'dfp');
}
if (config.getConfig('brandCategoryTranslation.translationFile')) { getHook('registerAdserver').before(notifyTranslationModule); }
/**
* 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 {Object} bid winning bid object to append parameters from
* @param {Object} options Options which should be used to construct the URL (used for custom params).
* @return {string} video url
*/
function buildUrlFromAdserverUrlComponents(components, bid, options) {
const descriptionUrl = getDescriptionUrl(bid, components, 'search');
if (descriptionUrl) {
components.search.description_url = descriptionUrl;
}
components.search.cust_params = getCustParams(bid, options, components.search.cust_params);
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 {Object} 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) {
return deepAccess(components, `${prop}.description_url`) || encodeURIComponent(dep.ri().page);
}
/**
* Returns the encoded `cust_params` from the bid.adserverTargeting and adds the `hb_uuid`, and `hb_cache_id`. Optionally the options.params.cust_params
* @param {Object} bid
* @param {Object} options this is the options passed in from the `buildDfpVideoUrl` function
* @return {Object} Encoded key value pairs for cust_params
*/
function getCustParams(bid, options, urlCustParams) {
const adserverTargeting = (bid && bid.adserverTargeting) || {};
let allTargetingData = {};
const adUnit = options && options.adUnit;
if (adUnit) {
let allTargeting = targeting.getAllTargeting(adUnit.code);
allTargetingData = (allTargeting) ? allTargeting[adUnit.code] : {};
}
const prebidTargetingSet = Object.assign({},
// Why are we adding standard keys here ? Refer https://github.com/prebid/Prebid.js/issues/3664
{ hb_uuid: bid && bid.videoCacheKey },
// hb_cache_id became optional in prebid 5.0 after 4.x enabled the concept of optional keys. Discussion led to reversing the prior expectation of deprecating hb_uuid
{ hb_cache_id: bid && bid.videoCacheKey },
allTargetingData,
adserverTargeting,
);
// TODO: WTF is this? just firing random events, guessing at the argument, hoping noone notices?
events.emit(EVENTS.SET_TARGETING, {[adUnit.code]: prebidTargetingSet});
// merge the prebid + publisher targeting sets
const publisherTargetingSet = deepAccess(options, 'params.cust_params');
const targetingSet = Object.assign({}, prebidTargetingSet, publisherTargetingSet);
let encodedParams = encodeURIComponent(formatQS(targetingSet));
if (urlCustParams) {
encodedParams = urlCustParams + '%26' + encodedParams;
}
return encodedParams;
}
registerVideoSupport('dfp', {
buildVideoUrl: buildDfpVideoUrl,
});