@streamlayer/feature-advertisement 1.1.1-rc.1350 → 1.1.1-rc.2.pf58ff685
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/advertisement.js +24 -9
- package/lib/cjs/advertisement.js +24 -9
- package/lib/cjs/index.d.ts +2 -0
- package/lib/cjs/index.js +2 -0
- package/lib/cjs/units/ad-macros.d.ts +7 -0
- package/lib/cjs/units/ad-macros.js +91 -0
- package/lib/cjs/units/external-ad.d.ts +69 -0
- package/lib/cjs/units/external-ad.js +93 -62
- package/lib/cjs/units/external-pause-ad.js +7 -1
- package/lib/cjs/units/gam.d.ts +11 -1
- package/lib/cjs/units/gam.js +65 -17
- package/lib/cjs/units/parse-vast-extensions.d.ts +10 -1
- package/lib/cjs/units/parse-vast-extensions.js +5 -1
- package/lib/cjs/units/sponsor-logo.d.ts +78 -0
- package/lib/cjs/units/sponsor-logo.js +194 -0
- package/lib/cjs/units/ssai-detector.js +27 -11
- package/lib/cjs/units/ssai-log.d.ts +16 -0
- package/lib/cjs/units/ssai-log.js +23 -0
- package/lib/cjs/units/ssai-providers.js +48 -11
- package/lib/classic/advertisement.js +34 -17
- package/lib/classic/cjs/advertisement.js +34 -17
- package/lib/classic/cjs/index.js +2 -0
- package/lib/classic/cjs/units/ad-macros.js +115 -0
- package/lib/classic/cjs/units/external-ad.js +111 -64
- package/lib/classic/cjs/units/external-pause-ad.js +5 -1
- package/lib/classic/cjs/units/gam.js +73 -10
- package/lib/classic/cjs/units/parse-vast-extensions.js +5 -1
- package/lib/classic/cjs/units/sponsor-logo.js +201 -0
- package/lib/classic/cjs/units/ssai-detector.js +33 -8
- package/lib/classic/cjs/units/ssai-log.js +23 -0
- package/lib/classic/cjs/units/ssai-providers.js +86 -24
- package/lib/classic/index.js +1 -0
- package/lib/classic/units/ad-macros.js +113 -0
- package/lib/classic/units/external-ad.js +109 -65
- package/lib/classic/units/external-pause-ad.js +5 -1
- package/lib/classic/units/gam.js +73 -10
- package/lib/classic/units/parse-vast-extensions.js +5 -1
- package/lib/classic/units/sponsor-logo.js +197 -0
- package/lib/classic/units/ssai-detector.js +33 -8
- package/lib/classic/units/ssai-log.js +21 -0
- package/lib/classic/units/ssai-providers.js +86 -24
- package/lib/index.d.ts +2 -0
- package/lib/index.js +1 -0
- package/lib/units/ad-macros.d.ts +7 -0
- package/lib/units/ad-macros.js +87 -0
- package/lib/units/external-ad.d.ts +69 -0
- package/lib/units/external-ad.js +92 -64
- package/lib/units/external-pause-ad.js +7 -1
- package/lib/units/gam.d.ts +11 -1
- package/lib/units/gam.js +65 -17
- package/lib/units/parse-vast-extensions.d.ts +10 -1
- package/lib/units/parse-vast-extensions.js +5 -1
- package/lib/units/sponsor-logo.d.ts +78 -0
- package/lib/units/sponsor-logo.js +188 -0
- package/lib/units/ssai-detector.js +27 -11
- package/lib/units/ssai-log.d.ts +16 -0
- package/lib/units/ssai-log.js +18 -0
- package/lib/units/ssai-providers.js +48 -11
- package/lib/vast-client.d.ts +9 -0
- package/package.json +8 -8
|
@@ -29,6 +29,7 @@ require('@streamlayer/sdk-web-api/classic');
|
|
|
29
29
|
var VMAP = require('vmap');
|
|
30
30
|
var gam = require('./gam.js');
|
|
31
31
|
var parseVastExtensions = require('./parse-vast-extensions.js');
|
|
32
|
+
var sponsorLogo = require('./sponsor-logo.js');
|
|
32
33
|
|
|
33
34
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
34
35
|
|
|
@@ -60,7 +61,7 @@ const withTimeout = (promise, ms) => {
|
|
|
60
61
|
* Get the first valid creative from an ad
|
|
61
62
|
*/
|
|
62
63
|
const getCreative = ad => {
|
|
63
|
-
return ad.creatives.find(creative => creative
|
|
64
|
+
return ad.creatives.find(creative => getMediaFileUrl(creative));
|
|
64
65
|
};
|
|
65
66
|
/**
|
|
66
67
|
* Get the URL of the first media file in the creative
|
|
@@ -83,32 +84,10 @@ const getClickThrough = creative => {
|
|
|
83
84
|
// @ts-ignore — vast-client types `videoClickThroughURLTemplate` loosely
|
|
84
85
|
return (creative === null || creative === void 0 || (_creative$videoClickT = creative.videoClickThroughURLTemplate) === null || _creative$videoClickT === void 0 ? void 0 : _creative$videoClickT.url) || undefined;
|
|
85
86
|
};
|
|
86
|
-
/**
|
|
87
|
-
* Extract every `<Companion>` variation of the first `<CompanionAds>` creative that carries a
|
|
88
|
-
* `<StaticResource>`, in document order. `width`/`height` are XML attributes (strings at
|
|
89
|
-
* runtime), normalised to numbers so the size mapping can compare them.
|
|
90
|
-
*/
|
|
91
|
-
const getCompanions = ad => {
|
|
92
|
-
const creative = ad.creatives.find(item => item.type === 'companion');
|
|
93
|
-
return ((creative === null || creative === void 0 ? void 0 : creative.variations) || []).reduce((acc, variation) => {
|
|
94
|
-
var _variation$staticReso, _variation$companionC;
|
|
95
|
-
const imageUrl = variation === null || variation === void 0 || (_variation$staticReso = variation.staticResources) === null || _variation$staticReso === void 0 || (_variation$staticReso = _variation$staticReso[0]) === null || _variation$staticReso === void 0 ? void 0 : _variation$staticReso.url;
|
|
96
|
-
if (!imageUrl) {
|
|
97
|
-
return acc;
|
|
98
|
-
}
|
|
99
|
-
return acc.concat({
|
|
100
|
-
imageUrl,
|
|
101
|
-
clickUrl: (_variation$companionC = variation.companionClickThroughURLTemplate) !== null && _variation$companionC !== void 0 ? _variation$companionC : undefined,
|
|
102
|
-
width: Number(variation.width) || undefined,
|
|
103
|
-
height: Number(variation.height) || undefined,
|
|
104
|
-
creative,
|
|
105
|
-
variation
|
|
106
|
-
});
|
|
107
|
-
}, []);
|
|
108
|
-
};
|
|
109
87
|
/** Product companion mapping: the 300x75 companion is the logo, the banner-sized one the media. */
|
|
110
88
|
const LOGO_COMPANION_WIDTH = 300;
|
|
111
89
|
const LOGO_COMPANION_HEIGHT = 75;
|
|
90
|
+
const isLogoSized = companion => (companion === null || companion === void 0 ? void 0 : companion.width) === LOGO_COMPANION_WIDTH && (companion === null || companion === void 0 ? void 0 : companion.height) === LOGO_COMPANION_HEIGHT;
|
|
112
91
|
const companionArea = companion => (companion.width || 0) * (companion.height || 0);
|
|
113
92
|
/**
|
|
114
93
|
* Split a `<CompanionAds>` creative's variations into the logo and media slots.
|
|
@@ -116,9 +95,18 @@ const companionArea = companion => (companion.width || 0) * (companion.height ||
|
|
|
116
95
|
* The pair mapping activates only for a tag that ships an exact 300x75 companion next to at
|
|
117
96
|
* least one other. Deliberately conservative pending broader product sizes: on any other tag
|
|
118
97
|
* no companion is promoted to the logo slot and the first one stays the media/strip candidate.
|
|
98
|
+
*
|
|
99
|
+
* Both halves of that gate are load-bearing and neither is an aspect rule: a 320x80 companion is
|
|
100
|
+
* the same 4:1 shape as 300x75 and is still rejected, and a lone 300x75 companion is rejected too
|
|
101
|
+
* (the Standard Ad Sidebar recovers that one as `AdData.loneLogo` when a Linear / NonLinear is
|
|
102
|
+
* the media, so it is not needed here).
|
|
103
|
+
* This is **narrower than iOS**, which has no size rule at all and fills its logo slot from a
|
|
104
|
+
* single companion — see `docs/wiki/programmatic-ads-display-ios-parity.md`. Exported for
|
|
105
|
+
* `external-ad.spec.ts` only; `src/index.ts` re-exports names explicitly, so this stays out of the
|
|
106
|
+
* package's public API.
|
|
119
107
|
*/
|
|
120
108
|
const selectCompanions = companions => {
|
|
121
|
-
const logo = companions.length > 1 ? companions.find(companion => companion
|
|
109
|
+
const logo = companions.length > 1 ? companions.find(companion => isLogoSized(companion)) : undefined;
|
|
122
110
|
if (!logo) {
|
|
123
111
|
return {
|
|
124
112
|
logo: undefined,
|
|
@@ -131,42 +119,27 @@ const selectCompanions = companions => {
|
|
|
131
119
|
media
|
|
132
120
|
};
|
|
133
121
|
};
|
|
134
|
-
/**
|
|
135
|
-
* Extract the first `<NonLinearAds>` creative's static-image variation. Both the creative (which
|
|
136
|
-
* owns the `<TrackingEvents>`) and the variation (which owns the click-through) are kept —
|
|
137
|
-
* `VASTTracker` needs both.
|
|
138
|
-
*
|
|
139
|
-
* `@dailymotion/vast-client` 6.4.2 emits `nonlinearClickThroughURLTemplate` (lower-case `l`),
|
|
140
|
-
* which the `@types/vast-client` shim declares as `nonLinearClickThroughURLTemplate` — the
|
|
141
|
-
* runtime spelling is authoritative.
|
|
142
|
-
*/
|
|
143
|
-
const getNonLinear = ad => {
|
|
144
|
-
var _creative$variations, _variation$nonlinearC;
|
|
145
|
-
const creative = ad.creatives.find(item => item.type === 'nonlinear');
|
|
146
|
-
const variation = creative === null || creative === void 0 || (_creative$variations = creative.variations) === null || _creative$variations === void 0 ? void 0 : _creative$variations[0];
|
|
147
|
-
const imageUrl = variation === null || variation === void 0 ? void 0 : variation.staticResource;
|
|
148
|
-
if (!creative || !variation || !imageUrl) {
|
|
149
|
-
return undefined;
|
|
150
|
-
}
|
|
151
|
-
return {
|
|
152
|
-
imageUrl,
|
|
153
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
154
|
-
// @ts-ignore — see the runtime-vs-shim spelling note above
|
|
155
|
-
clickUrl: (_variation$nonlinearC = variation.nonlinearClickThroughURLTemplate) !== null && _variation$nonlinearC !== void 0 ? _variation$nonlinearC : undefined,
|
|
156
|
-
width: Number(variation.width) || undefined,
|
|
157
|
-
height: Number(variation.height) || undefined,
|
|
158
|
-
creative,
|
|
159
|
-
variation
|
|
160
|
-
};
|
|
161
|
-
};
|
|
162
122
|
/**
|
|
163
123
|
* Map a parsed VAST response into our `AdData` shape. Shared by both acquisition
|
|
164
124
|
* paths — the GAM/VMAP tag flow and the Prebid auction flow — so the two always
|
|
165
|
-
* produce identical downstream data.
|
|
166
|
-
*
|
|
125
|
+
* produce identical downstream data.
|
|
126
|
+
*
|
|
127
|
+
* Returns `undefined` for a no-ad, which is two distinct shapes: a response with no ad carrying
|
|
128
|
+
* creatives, and an ad whose creatives carry no RENDERABLE asset — no linear `<MediaFile>`, no
|
|
129
|
+
* `<NonLinear>` still and no `<Companion>` image. Both callers `filter(Boolean)` the result, so a
|
|
130
|
+
* no-ad leaves `ads: []` and the card keeps whatever the server configured for it.
|
|
131
|
+
*
|
|
132
|
+
* Exported for `vast-mapping.spec.ts`, which drives it over the same fixture corpus the iOS suite
|
|
133
|
+
* uses. `src/index.ts` re-exports names explicitly, so this stays out of the package's public API.
|
|
167
134
|
*/
|
|
168
135
|
const buildAdData = (parsedVAST, order) => {
|
|
169
136
|
var _validAd$extensions$f;
|
|
137
|
+
// Defensive, and unreachable through either production path: `@dailymotion/vast-client` discards
|
|
138
|
+
// an ad whose creatives all fail to parse (empty `<Linear>`, empty/missing `<Creatives>`, unknown
|
|
139
|
+
// creative element) before `parseVAST` resolves, so it never yields an Ad with zero creatives.
|
|
140
|
+
// Kept because it costs nothing and `buildAdData` is also reachable from tests with a hand-built
|
|
141
|
+
// response — see the `invalid_ad` case in `vast-mapping.spec.ts`, which pins the parser contract
|
|
142
|
+
// this relies on rather than pretending to exercise the guard.
|
|
170
143
|
const validAd = parsedVAST.ads.find(ads => ads.creatives.length > 0);
|
|
171
144
|
if (!validAd) {
|
|
172
145
|
log.warn({
|
|
@@ -181,13 +154,52 @@ const buildAdData = (parsedVAST, order) => {
|
|
|
181
154
|
const parsedExtension = parseVastExtensions.parseVastExtension(extension);
|
|
182
155
|
const creative = getCreative(validAd);
|
|
183
156
|
const mediaFileUrl = getMediaFileUrl(creative);
|
|
184
|
-
const
|
|
157
|
+
const images = sponsorLogo.collectAssets(validAd);
|
|
158
|
+
const companions = images.filter(asset => asset.origin === 'companion');
|
|
159
|
+
const nonLinear = images.find(asset => asset.origin === 'nonlinear');
|
|
160
|
+
// An ad the parser KEPT whose creatives carry nothing any shell can draw is a no-ad, exactly
|
|
161
|
+
// like a response with no ad at all. vast-client only discards a creative it cannot parse; a
|
|
162
|
+
// `<NonLinear>` or `<Companion>` whose `<StaticResource>` body is empty parses fine and just
|
|
163
|
+
// yields no url, so the guard above cannot catch it. Without this the fabricated promotion still
|
|
164
|
+
// took the slot and drew the ad's TEXT — title and description over an empty media box — which
|
|
165
|
+
// is a billable impression for a creative that does not exist.
|
|
166
|
+
//
|
|
167
|
+
// "Renderable" is the union of the three slots the shells can fill: a linear `<MediaFile>`, a
|
|
168
|
+
// `<NonLinear>` still, or a `<Companion>` image. `collectAssets` already drops the imageless
|
|
169
|
+
// ones, so a non-empty `companions` means at least one drawable image. A tag carrying only a
|
|
170
|
+
// `<Mezzanine>` never reaches here — the parser drops that Linear itself — but it lands in the
|
|
171
|
+
// same place either way.
|
|
172
|
+
if (!mediaFileUrl && !nonLinear && companions.length === 0) {
|
|
173
|
+
log.warn({
|
|
174
|
+
order,
|
|
175
|
+
adId: validAd.id,
|
|
176
|
+
creatives: validAd.creatives.length
|
|
177
|
+
}, 'vast ad carries no renderable asset (no media file, no nonlinear, no companion) — treated as no-ad');
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
185
180
|
const {
|
|
186
181
|
logo,
|
|
187
182
|
media: mediaCompanion
|
|
188
183
|
} = selectCompanions(companions);
|
|
189
|
-
const nonLinear = getNonLinear(validAd);
|
|
190
184
|
const clickThrough = getClickThrough(creative) || (nonLinear === null || nonLinear === void 0 ? void 0 : nonLinear.clickUrl) || (mediaCompanion === null || mediaCompanion === void 0 ? void 0 : mediaCompanion.clickUrl);
|
|
185
|
+
// Mirror iOS's greedy `pool.select([hero, .cardLogo])`: the hero claims first and a claimed
|
|
186
|
+
// asset is gone for later roles. Web's hero precedence (`createActiveAdFromVast`) is linear
|
|
187
|
+
// video → pair media companion (unreachable here: a `logo` short-circuits the whole selection)
|
|
188
|
+
// → NonLinear still → lone companion, so a companion is only unavailable to the logo slot when
|
|
189
|
+
// it IS the hero, i.e. when the tag ships neither. `selectCardLogo` already filters to companion
|
|
190
|
+
// origins, so the NonLinear never needs excluding.
|
|
191
|
+
//
|
|
192
|
+
// Excluding `companions[0]` (which is what `mediaCompanion` is when there is no `logo`) rather
|
|
193
|
+
// than the largest-area asset is a deliberate difference from iOS's `areaDominant` hero: web's
|
|
194
|
+
// content slot draws the FIRST companion in document order, so document order is the correct
|
|
195
|
+
// criterion for "what the hero already claimed" on this platform.
|
|
196
|
+
const companionIsHero = !mediaFileUrl && !nonLinear;
|
|
197
|
+
const cardLogoAsset = logo ? undefined : sponsorLogo.selectCardLogo(validAd, companionIsHero ? [mediaCompanion] : []);
|
|
198
|
+
// Share the selected resource wrapper so drawn-companion identity remains stable.
|
|
199
|
+
const cardLogo = cardLogoAsset ? companions.find(item => item.variation === cardLogoAsset.variation && item.imageUrl === cardLogoAsset.imageUrl) : undefined;
|
|
200
|
+
// Same collected instance as `companion` (identity matters to `drawnCompanions`). A lone
|
|
201
|
+
// companion never satisfies the pair rule, so this can only fill a slot `logo` left empty.
|
|
202
|
+
const loneLogo = companions.length === 1 && !companionIsHero && isLogoSized(companions[0]) ? companions[0] : undefined;
|
|
191
203
|
log.info({
|
|
192
204
|
order,
|
|
193
205
|
adId: validAd.id,
|
|
@@ -207,6 +219,14 @@ const buildAdData = (parsedVAST, order) => {
|
|
|
207
219
|
size: `${mediaCompanion.width}x${mediaCompanion.height}`,
|
|
208
220
|
url: mediaCompanion.imageUrl
|
|
209
221
|
} : undefined,
|
|
222
|
+
cardLogo: cardLogo ? {
|
|
223
|
+
size: `${cardLogo.width}x${cardLogo.height}`,
|
|
224
|
+
url: cardLogo.imageUrl
|
|
225
|
+
} : undefined,
|
|
226
|
+
loneLogo: loneLogo ? {
|
|
227
|
+
size: `${loneLogo.width}x${loneLogo.height}`,
|
|
228
|
+
url: loneLogo.imageUrl
|
|
229
|
+
} : undefined,
|
|
210
230
|
clickThrough: !!clickThrough
|
|
211
231
|
}
|
|
212
232
|
}, 'vast ad parsed');
|
|
@@ -216,6 +236,8 @@ const buildAdData = (parsedVAST, order) => {
|
|
|
216
236
|
mediaFileUrl,
|
|
217
237
|
companion: companions[0],
|
|
218
238
|
logo,
|
|
239
|
+
cardLogo,
|
|
240
|
+
loneLogo,
|
|
219
241
|
mediaCompanion,
|
|
220
242
|
nonLinear,
|
|
221
243
|
clickThrough,
|
|
@@ -244,8 +266,12 @@ const createRequestId = () => `sl-web-${Date.now().toString(36)}-${Math.random()
|
|
|
244
266
|
* - `storedauctionresponse` → `imp.ext.prebid.storedauctionresponse.id` (staging mock).
|
|
245
267
|
* - `test=1` → top-level `test` flag.
|
|
246
268
|
* - `w` / `h` → video dimensions (default 1920×1080).
|
|
269
|
+
*
|
|
270
|
+
* The host's GDPR consent (`AdTagParams`) rides the OpenRTB 2.5 extension fields Prebid Server
|
|
271
|
+
* reads — `regs.ext.gdpr` and `user.ext.consent` — the same signal the `${GDPR}` /
|
|
272
|
+
* `${GDPR_CONSENT}` macros carry for a VAST tag. Each is sent only when the host passed it.
|
|
247
273
|
*/
|
|
248
|
-
const buildAuctionRequest = url => {
|
|
274
|
+
const buildAuctionRequest = (url, adTagParams) => {
|
|
249
275
|
var _ref, _params$get, _params$get2;
|
|
250
276
|
const params = url.searchParams;
|
|
251
277
|
const width = Number(params.get('w')) || 1920;
|
|
@@ -292,6 +318,20 @@ const buildAuctionRequest = url => {
|
|
|
292
318
|
if (params.get('test') === '1') {
|
|
293
319
|
request.test = 1;
|
|
294
320
|
}
|
|
321
|
+
if ((adTagParams === null || adTagParams === void 0 ? void 0 : adTagParams.gdprApplies) !== undefined) {
|
|
322
|
+
request.regs = {
|
|
323
|
+
ext: {
|
|
324
|
+
gdpr: adTagParams.gdprApplies ? 1 : 0
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
if (adTagParams !== null && adTagParams !== void 0 && adTagParams.tcString) {
|
|
329
|
+
request.user = {
|
|
330
|
+
ext: {
|
|
331
|
+
consent: adTagParams.tcString
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
}
|
|
295
335
|
return request;
|
|
296
336
|
};
|
|
297
337
|
/**
|
|
@@ -300,14 +340,14 @@ const buildAuctionRequest = url => {
|
|
|
300
340
|
* so the winner plays first (RFC §2). A single bad bid must not drop the batch.
|
|
301
341
|
*/
|
|
302
342
|
const fetchPrebidAds = /*#__PURE__*/function () {
|
|
303
|
-
var _ref2 = _rollupPluginBabelHelpers.asyncToGenerator(function* (prebidUrl) {
|
|
343
|
+
var _ref2 = _rollupPluginBabelHelpers.asyncToGenerator(function* (prebidUrl, adTagParams) {
|
|
304
344
|
var _auction$seatbid, _params$get3, _params$get4;
|
|
305
345
|
const response = yield fetch(prebidUrl.toString(), {
|
|
306
346
|
method: 'POST',
|
|
307
347
|
headers: {
|
|
308
348
|
'Content-Type': 'application/json'
|
|
309
349
|
},
|
|
310
|
-
body: JSON.stringify(buildAuctionRequest(prebidUrl))
|
|
350
|
+
body: JSON.stringify(buildAuctionRequest(prebidUrl, adTagParams))
|
|
311
351
|
});
|
|
312
352
|
if (!response.ok) {
|
|
313
353
|
throw new Error(`prebid auction failed with status ${response.status}`);
|
|
@@ -350,7 +390,7 @@ const fetchPrebidAds = /*#__PURE__*/function () {
|
|
|
350
390
|
muted
|
|
351
391
|
};
|
|
352
392
|
});
|
|
353
|
-
return function fetchPrebidAds(_x) {
|
|
393
|
+
return function fetchPrebidAds(_x, _x2) {
|
|
354
394
|
return _ref2.apply(this, arguments);
|
|
355
395
|
};
|
|
356
396
|
}();
|
|
@@ -371,13 +411,17 @@ const externalAd = instance => {
|
|
|
371
411
|
return undefined;
|
|
372
412
|
}
|
|
373
413
|
})();
|
|
414
|
+
// The host's CURRENT macro values, read per request rather than baked into the store: the
|
|
415
|
+
// store is cached by url for the session, and consent routinely arrives after the SDK
|
|
416
|
+
// starts (a CMP answers asynchronously) or changes mid-session.
|
|
417
|
+
const adTagParams = instance.sdk.options.get().adTagParams;
|
|
374
418
|
// Prebid links hit our Prebid Server auction endpoint instead of a VAST tag.
|
|
375
419
|
// Run the OpenRTB auction and parse the winning bid's inline VAST (`adm`) into
|
|
376
420
|
// the same `AdStore` shape the GAM/VMAP path returns. See rfcs/prebid.
|
|
377
421
|
if (parsedAdUrl && isPrebidUrl(parsedAdUrl)) {
|
|
378
|
-
return fetchPrebidAds(parsedAdUrl);
|
|
422
|
+
return fetchPrebidAds(parsedAdUrl, adTagParams);
|
|
379
423
|
}
|
|
380
|
-
const generateUrl = gam.gam(adUrl);
|
|
424
|
+
const generateUrl = gam.gam(adUrl, undefined, adTagParams);
|
|
381
425
|
const generatedUrl = yield generateUrl();
|
|
382
426
|
const noncedLink = generatedUrl === null || generatedUrl === void 0 ? void 0 : generatedUrl.url;
|
|
383
427
|
if (!noncedLink) {
|
|
@@ -443,11 +487,14 @@ const externalAd = instance => {
|
|
|
443
487
|
nonceManager
|
|
444
488
|
};
|
|
445
489
|
});
|
|
446
|
-
return function fetcher(
|
|
490
|
+
return function fetcher(_x3) {
|
|
447
491
|
return _ref3.apply(this, arguments);
|
|
448
492
|
};
|
|
449
493
|
}()
|
|
450
494
|
});
|
|
451
495
|
};
|
|
452
496
|
|
|
497
|
+
exports.buildAdData = buildAdData;
|
|
498
|
+
exports.buildAuctionRequest = buildAuctionRequest;
|
|
453
499
|
exports.externalAd = externalAd;
|
|
500
|
+
exports.selectCompanions = selectCompanions;
|
|
@@ -14,6 +14,7 @@ var classic$1 = require('@streamlayer/sdk-web-interfaces/classic');
|
|
|
14
14
|
var classic = require('@streamlayer/sdk-web-logger/classic');
|
|
15
15
|
require('@streamlayer/sdk-web-api/classic');
|
|
16
16
|
var vastClient$1 = require('@dailymotion/vast-client');
|
|
17
|
+
var adMacros = require('./ad-macros.js');
|
|
17
18
|
|
|
18
19
|
const FULLBLEED_VIDEO_TEMPLATES = ['fullbleed-video', 'fullbleed-video-center', 'fullbleed-video-left'];
|
|
19
20
|
/**
|
|
@@ -128,7 +129,10 @@ const externalPauseAd = instance => {
|
|
|
128
129
|
var _options$template2, _parsedVAST$ads$find;
|
|
129
130
|
// `resolveAll: true` follows every `VASTAdTagURI` wrapper chain and resolves all
|
|
130
131
|
// Ads (the default only resolves the first standalone Ad and stops at the wrapper).
|
|
131
|
-
|
|
132
|
+
// Pause-ad tags do not go through `gam()`, so fill the macros here — with the host's values
|
|
133
|
+
// as they are NOW, since this fetcher re-runs on every pause. The store key stays the
|
|
134
|
+
// configured url: `ExposedPauseAd` compares against it.
|
|
135
|
+
const parsedVAST = yield vastClient.get(adMacros.substituteAdTagMacros(adUrl, instance.sdk.options.get().adTagParams), {
|
|
132
136
|
resolveAll: true
|
|
133
137
|
});
|
|
134
138
|
const template = (_options$template2 = options.template) !== null && _options$template2 !== void 0 ? _options$template2 : 'default';
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var _rollupPluginBabelHelpers = require('../_virtual/_rollupPluginBabelHelpers.js');
|
|
4
|
+
require('core-js/modules/es.error.cause.js');
|
|
4
5
|
require('core-js/modules/es.array.iterator.js');
|
|
6
|
+
require('core-js/modules/es.promise.js');
|
|
5
7
|
require('core-js/modules/web.dom-collections.iterator.js');
|
|
6
8
|
require('core-js/modules/web.url.js');
|
|
7
9
|
require('core-js/modules/web.url.to-json.js');
|
|
@@ -11,11 +13,73 @@ require('core-js/modules/web.url-search-params.has.js');
|
|
|
11
13
|
require('core-js/modules/web.url-search-params.size.js');
|
|
12
14
|
var classic = require('@streamlayer/sdk-web-storage/classic');
|
|
13
15
|
var uuid = require('uuid');
|
|
16
|
+
var adMacros = require('./ad-macros.js');
|
|
14
17
|
|
|
15
18
|
const urlIsNotGam = url => {
|
|
16
19
|
return url.origin !== 'https://pubads.g.doubleclick.net';
|
|
17
20
|
};
|
|
18
|
-
const
|
|
21
|
+
const palScriptUrl = 'https://imasdk.googleapis.com/pal/sdkloader/pal.js';
|
|
22
|
+
const getPal = () => {
|
|
23
|
+
// @ts-expect-error Google PAL is supplied by the host or the script loader below.
|
|
24
|
+
return typeof goog === 'undefined' ? undefined : goog.pal;
|
|
25
|
+
};
|
|
26
|
+
let palReady;
|
|
27
|
+
const ensurePal = /*#__PURE__*/function () {
|
|
28
|
+
var _ref = _rollupPluginBabelHelpers.asyncToGenerator(function* () {
|
|
29
|
+
if (getPal()) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (!palReady) {
|
|
33
|
+
palReady = new Promise((resolve, reject) => {
|
|
34
|
+
if (typeof document === 'undefined') {
|
|
35
|
+
reject(new Error('Google PAL requires a browser document'));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const existing = document.querySelector(`script[src="${palScriptUrl}"]`);
|
|
39
|
+
const script = existing || document.createElement('script');
|
|
40
|
+
const finish = error => {
|
|
41
|
+
clearTimeout(timeout);
|
|
42
|
+
script.removeEventListener('load', onLoad);
|
|
43
|
+
script.removeEventListener('error', onError);
|
|
44
|
+
if (error) {
|
|
45
|
+
if (!existing) {
|
|
46
|
+
script.remove();
|
|
47
|
+
}
|
|
48
|
+
reject(error);
|
|
49
|
+
} else {
|
|
50
|
+
resolve();
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
const onLoad = () => finish(getPal() ? undefined : new Error('Google PAL did not initialize'));
|
|
54
|
+
const onError = () => finish(new Error('Google PAL failed to load'));
|
|
55
|
+
const timeout = setTimeout(() => finish(new Error('Google PAL loading timed out')), 10000);
|
|
56
|
+
script.addEventListener('load', onLoad);
|
|
57
|
+
script.addEventListener('error', onError);
|
|
58
|
+
if (!existing) {
|
|
59
|
+
script.async = true;
|
|
60
|
+
script.src = palScriptUrl;
|
|
61
|
+
document.head.appendChild(script);
|
|
62
|
+
}
|
|
63
|
+
}).catch(error => {
|
|
64
|
+
palReady = undefined;
|
|
65
|
+
throw error;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
yield palReady;
|
|
69
|
+
palReady = undefined;
|
|
70
|
+
});
|
|
71
|
+
return function ensurePal() {
|
|
72
|
+
return _ref.apply(this, arguments);
|
|
73
|
+
};
|
|
74
|
+
}();
|
|
75
|
+
const gam = (rawUrl, adContainer, adTagParams) => {
|
|
76
|
+
// Every VAST/VMAP tag funnels through here, so this is where the macros get filled — for GAM and
|
|
77
|
+
// for any other vendor alike. Studio stores a programmatic tag verbatim, so an unexpanded
|
|
78
|
+
// `[timestamp]` / `[clickmacro]` / `${GDPR}` would otherwise reach the ad server literal, and a
|
|
79
|
+
// literal macro comes back copied into the vendor's click-through. `adTagParams` are the host's
|
|
80
|
+
// consent and click values, read by the caller at request time. The GAM `[placeholder]` tokens
|
|
81
|
+
// are left alone here and filled per parameter below.
|
|
82
|
+
const url = adMacros.substituteAdTagMacros(rawUrl, adTagParams);
|
|
19
83
|
const parsedUrl = (() => {
|
|
20
84
|
try {
|
|
21
85
|
const parsedUrl = new URL(url);
|
|
@@ -36,22 +100,21 @@ const gam = (url, adContainer) => {
|
|
|
36
100
|
};
|
|
37
101
|
});
|
|
38
102
|
}
|
|
39
|
-
|
|
40
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
41
|
-
const pal = typeof goog === 'undefined' ? undefined : goog.pal;
|
|
42
|
-
if (!pal || !parsedUrl) {
|
|
103
|
+
if (!parsedUrl) {
|
|
43
104
|
// eslint-disable-next-line require-await
|
|
44
105
|
return /*#__PURE__*/_rollupPluginBabelHelpers.asyncToGenerator(function* () {
|
|
45
106
|
return undefined;
|
|
46
107
|
});
|
|
47
108
|
}
|
|
48
|
-
const consentSettings = new pal.ConsentSettings();
|
|
49
|
-
consentSettings.allowStorage = false;
|
|
50
|
-
const nonceLoader = new pal.NonceLoader(consentSettings);
|
|
51
109
|
const generateUrl = /*#__PURE__*/function () {
|
|
52
|
-
var
|
|
110
|
+
var _ref4 = _rollupPluginBabelHelpers.asyncToGenerator(function* () {
|
|
53
111
|
try {
|
|
54
112
|
var _adContainer$clientHe, _adContainer$clientWi;
|
|
113
|
+
yield ensurePal();
|
|
114
|
+
const pal = getPal();
|
|
115
|
+
const consentSettings = new pal.ConsentSettings();
|
|
116
|
+
consentSettings.allowStorage = false;
|
|
117
|
+
const nonceLoader = new pal.NonceLoader(consentSettings);
|
|
55
118
|
const parsedUrl = new URL(url);
|
|
56
119
|
const request = new pal.NonceRequest();
|
|
57
120
|
let deviceId = classic.safeLocalStorage().getItem('sl-device-id') || uuid.v4();
|
|
@@ -101,7 +164,7 @@ const gam = (url, adContainer) => {
|
|
|
101
164
|
}
|
|
102
165
|
});
|
|
103
166
|
return function generateUrl() {
|
|
104
|
-
return
|
|
167
|
+
return _ref4.apply(this, arguments);
|
|
105
168
|
};
|
|
106
169
|
}();
|
|
107
170
|
return generateUrl;
|
|
@@ -6,6 +6,10 @@ require('core-js/modules/esnext.iterator.find.js');
|
|
|
6
6
|
require('core-js/modules/esnext.iterator.map.js');
|
|
7
7
|
var interactive_common_pb = require('@streamlayer/sl-eslib/interactive/interactive.common_pb');
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Extension root element name → the placement it names. Only `DoubleBox` is defined by the
|
|
11
|
+
* StreamLayer extension schema today; anything else is a vendor extension we do not interpret.
|
|
12
|
+
*/
|
|
9
13
|
const MAP_EXTENSION_NAME_AD_TYPE = {
|
|
10
14
|
DoubleBox: interactive_common_pb.AdType.SIDE_BY_SIDE
|
|
11
15
|
};
|
|
@@ -45,7 +49,7 @@ function parseVastExtension(root) {
|
|
|
45
49
|
};
|
|
46
50
|
return {
|
|
47
51
|
adName,
|
|
48
|
-
adType: MAP_EXTENSION_NAME_AD_TYPE[root.name]
|
|
52
|
+
adType: MAP_EXTENSION_NAME_AD_TYPE[root.name],
|
|
49
53
|
landscapeBackground: (_backgrounds$find = backgrounds.find(b => b.orientation === 'landscape')) === null || _backgrounds$find === void 0 ? void 0 : _backgrounds$find.url,
|
|
50
54
|
portraitBackground: (_backgrounds$find2 = backgrounds.find(b => b.orientation === 'portrait')) === null || _backgrounds$find2 === void 0 ? void 0 : _backgrounds$find2.url,
|
|
51
55
|
sponsorLogo: (_logos$find = logos.find(l => l.role === 'header')) === null || _logos$find === void 0 ? void 0 : _logos$find.url,
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
require('core-js/modules/es.array.push.js');
|
|
4
|
+
require('core-js/modules/esnext.iterator.constructor.js');
|
|
5
|
+
require('core-js/modules/esnext.iterator.filter.js');
|
|
6
|
+
require('core-js/modules/esnext.iterator.map.js');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Sponsor-logo slot selection for a **feature** ad unit (Insight, Prediction and Trivia today;
|
|
10
|
+
* Poll is the same shape and is a separate ticket).
|
|
11
|
+
*
|
|
12
|
+
* Deliberately NOT `selectCompanions` (`./external-ad.ts`). That rule is the *display*-unit rule:
|
|
13
|
+
* it promotes a companion to the logo slot only when the tag ships an exact 300x75 `<Companion>`
|
|
14
|
+
* next to at least one other. A feature ad-unit tag is typically a **single** creative — often a
|
|
15
|
+
* lone NonLinear `<StaticResource>` — where the ad IS the logo, which is exactly the shape that
|
|
16
|
+
* rule returns nothing for. `selectCompanions` stays untouched: SC-335's three shipped units
|
|
17
|
+
* depend on its exact behaviour and impression counts are billing-visible.
|
|
18
|
+
*
|
|
19
|
+
* Mirrors iOS `SLRVASTSlotRole.logo` + `SLRVASTAssetPool`
|
|
20
|
+
* (`StreamLayerSDK/Frameworks/SLRGAMFramework/Sources/VASTSlotFiller/`):
|
|
21
|
+
*
|
|
22
|
+
* - **Images only.** A video-only tag yields no logo. `<Icon>` is never projected — icons hang off
|
|
23
|
+
* the `<Linear>` creative, which this selector does not look at. A `<StaticResource>` the tag
|
|
24
|
+
* declares as a non-image `creativeType` is skipped too, so a companion that lists a script
|
|
25
|
+
* before its image still resolves to the image (see `isImageResource`).
|
|
26
|
+
* - **Origin-agnostic.** A lone NonLinear `<StaticResource>` is as valid as a `<Companion>`.
|
|
27
|
+
* - **Aspect-dominant** toward 1:1: the smallest `|log(assetAspect) - log(1)|` wins, ties broken by
|
|
28
|
+
* larger area, then by higher pixel density. Aspect is a *preference, never a filter* — the only
|
|
29
|
+
* hard constraint is the asset kind, so a badly shaped asset still wins if it is the only one.
|
|
30
|
+
* - Assets whose intrinsic size the tag never declared rank last, but still beat nothing.
|
|
31
|
+
*
|
|
32
|
+
* The module also hosts iOS's second logo role, `SLRVASTSlotRole.cardLogo` (`selectCardLogo`) —
|
|
33
|
+
* the same ranking narrowed to `<Companion>` origins, for the hero-card display units.
|
|
34
|
+
*/
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/triple-slash-reference
|
|
36
|
+
/// <reference path="../module.d.ts" />
|
|
37
|
+
/** `width`/`height` are XML attributes (strings at runtime); 0 and NaN both mean "not declared". */
|
|
38
|
+
const toSize = value => {
|
|
39
|
+
const size = Number(value);
|
|
40
|
+
return size > 0 ? size : undefined;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* VAST 4 `<CompanionAds pxratio>`; `@dailymotion/vast-client` defaults it to `'1'` and the
|
|
44
|
+
* `@types/vast-client` shim does not declare it, hence the loose read.
|
|
45
|
+
*/
|
|
46
|
+
const toPxRatio = variation => {
|
|
47
|
+
return Number(variation.pxratio) || 1;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* `<StaticResource creativeType>`. The slot draws an `<img>`, so a resource the tag declares as
|
|
51
|
+
* anything else — the `application/x-shockwave-flash` or `application/javascript` creative a
|
|
52
|
+
* companion may list beside its image — must never fill it: it would swap the proto logo for a
|
|
53
|
+
* broken image AND still fire an impression for something nobody ever saw.
|
|
54
|
+
*
|
|
55
|
+
* An ABSENT type is not a rejection. VAST requires the attribute but real GAM image companions
|
|
56
|
+
* routinely omit it, and this module's only hard constraint is the asset kind, so an undeclared
|
|
57
|
+
* type stays a candidate. `@dailymotion/vast-client` writes `0` for a missing NonLinear
|
|
58
|
+
* `creativeType` and `null` for a missing companion one; both are falsy, hence the single guard.
|
|
59
|
+
*/
|
|
60
|
+
const isImageResource = creativeType => {
|
|
61
|
+
if (!creativeType) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
return String(creativeType).toLowerCase().indexOf('image/') === 0;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* The first resource a `<Companion>` declares as an image (or leaves undeclared). NOT
|
|
68
|
+
* `staticResources[0]`: the order in the tag is the advertiser's, and a non-image resource listed
|
|
69
|
+
* first must not hide a usable image behind it.
|
|
70
|
+
*/
|
|
71
|
+
const pickCompanionImage = variation => {
|
|
72
|
+
const resources = variation.staticResources || [];
|
|
73
|
+
for (let i = 0; i < resources.length; i++) {
|
|
74
|
+
const resource = resources[i];
|
|
75
|
+
if (resource && resource.url && isImageResource(resource.creativeType)) {
|
|
76
|
+
return resource.url;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return undefined;
|
|
80
|
+
};
|
|
81
|
+
/** Distance from square on a log scale, so 2:1 and 1:2 are equally far from 1:1. */
|
|
82
|
+
const aspectDistance = asset => {
|
|
83
|
+
if (!asset.width || !asset.height) {
|
|
84
|
+
// Unknown intrinsic size — rank last, but still a candidate.
|
|
85
|
+
return Infinity;
|
|
86
|
+
}
|
|
87
|
+
return Math.abs(Math.log(asset.width / asset.height));
|
|
88
|
+
};
|
|
89
|
+
const collectAssetsFrom = ad => {
|
|
90
|
+
const assets = [];
|
|
91
|
+
for (let i = 0; i < ad.creatives.length; i++) {
|
|
92
|
+
const creative = ad.creatives[i];
|
|
93
|
+
if (creative.type === 'companion') {
|
|
94
|
+
const companionCreative = creative;
|
|
95
|
+
const variations = companionCreative.variations || [];
|
|
96
|
+
for (let j = 0; j < variations.length; j++) {
|
|
97
|
+
const variation = variations[j];
|
|
98
|
+
const imageUrl = variation && pickCompanionImage(variation);
|
|
99
|
+
if (imageUrl) {
|
|
100
|
+
var _variation$companionC;
|
|
101
|
+
assets.push({
|
|
102
|
+
imageUrl,
|
|
103
|
+
clickUrl: (_variation$companionC = variation.companionClickThroughURLTemplate) !== null && _variation$companionC !== void 0 ? _variation$companionC : undefined,
|
|
104
|
+
width: toSize(variation.width),
|
|
105
|
+
height: toSize(variation.height),
|
|
106
|
+
origin: 'companion',
|
|
107
|
+
creative: companionCreative,
|
|
108
|
+
variation
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (creative.type === 'nonlinear') {
|
|
115
|
+
const nonLinearCreative = creative;
|
|
116
|
+
const variations = nonLinearCreative.variations || [];
|
|
117
|
+
for (let j = 0; j < variations.length; j++) {
|
|
118
|
+
const variation = variations[j];
|
|
119
|
+
// `type` is where the parser puts a `<NonLinear>`'s `creativeType`, and it is written last
|
|
120
|
+
// from the `<StaticResource>` element, so it describes `staticResource` whenever that is
|
|
121
|
+
// set. An HTML- or iframe-typed variation carries no `staticResource` and drops out here.
|
|
122
|
+
const imageUrl = variation && isImageResource(variation.type) ? variation.staticResource : undefined;
|
|
123
|
+
if (imageUrl) {
|
|
124
|
+
var _variation$nonlinearC;
|
|
125
|
+
assets.push({
|
|
126
|
+
imageUrl,
|
|
127
|
+
// 6.4.2 emits `nonlinearClickThroughURLTemplate` (lower-case `l`); the shim declares
|
|
128
|
+
// the upper-case spelling. The runtime one is authoritative — see `getNonLinear`.
|
|
129
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
130
|
+
// @ts-ignore
|
|
131
|
+
clickUrl: (_variation$nonlinearC = variation.nonlinearClickThroughURLTemplate) !== null && _variation$nonlinearC !== void 0 ? _variation$nonlinearC : undefined,
|
|
132
|
+
width: toSize(variation.width),
|
|
133
|
+
height: toSize(variation.height),
|
|
134
|
+
origin: 'nonlinear',
|
|
135
|
+
creative: nonLinearCreative,
|
|
136
|
+
variation
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return assets;
|
|
143
|
+
};
|
|
144
|
+
/**
|
|
145
|
+
* The shared ranking both roles use: aspect distance from 1:1, ties broken by larger area, then by
|
|
146
|
+
* higher pixel density. Aspect is a preference, never a filter — the best of a bad set still wins.
|
|
147
|
+
*/
|
|
148
|
+
const rankBest = assets => {
|
|
149
|
+
if (!assets.length) {
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
let best = assets[0];
|
|
153
|
+
let bestDistance = aspectDistance(best);
|
|
154
|
+
let bestArea = (best.width || 0) * (best.height || 0);
|
|
155
|
+
let bestPxRatio = toPxRatio(best.variation);
|
|
156
|
+
for (let i = 1; i < assets.length; i++) {
|
|
157
|
+
const asset = assets[i];
|
|
158
|
+
const distance = aspectDistance(asset);
|
|
159
|
+
const area = (asset.width || 0) * (asset.height || 0);
|
|
160
|
+
const pxRatio = toPxRatio(asset.variation);
|
|
161
|
+
const wins = distance !== bestDistance ? distance < bestDistance : area !== bestArea ? area > bestArea : pxRatio > bestPxRatio;
|
|
162
|
+
if (wins) {
|
|
163
|
+
best = asset;
|
|
164
|
+
bestDistance = distance;
|
|
165
|
+
bestArea = area;
|
|
166
|
+
bestPxRatio = pxRatio;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return best;
|
|
170
|
+
};
|
|
171
|
+
/**
|
|
172
|
+
* Pick the image a parsed VAST ad offers for a sponsor-logo slot, or `undefined` when it offers
|
|
173
|
+
* none (a video-only tag, or an empty response). A caller that gets `undefined` must fall back to
|
|
174
|
+
* its own proto/Studio logo rather than suppressing the card.
|
|
175
|
+
*/
|
|
176
|
+
const collectAssets = ad => collectAssetsFrom(ad);
|
|
177
|
+
const selectSponsorLogo = ad => rankBest(collectAssetsFrom(ad));
|
|
178
|
+
/**
|
|
179
|
+
* iOS `SLRVASTSlotRole.cardLogo` — the sponsor logo of a **hero card** (1:1 / 2:1 / Picture in
|
|
180
|
+
* Picture / Side-by-Side / Sponsor Frame). Same 1:1 aspect-dominant ranking as `.logo`, plus the
|
|
181
|
+
* one constraint that distinguishes the two roles:
|
|
182
|
+
*
|
|
183
|
+
* - **`requiredOrigin: .companion`.** A NonLinear image is a hero creative, never a logo. iOS's
|
|
184
|
+
* reason: a leftover hero-type asset — a second `<NonLinear>` overlay, or a stray image still in
|
|
185
|
+
* the pool after the hero role claimed its own — must never leak into the card's logo slot,
|
|
186
|
+
* where it would render at logo size as an unreadable smudge of the advertiser's key art.
|
|
187
|
+
* `nonlinear_full` is the fixture that pins it: `.logo` takes the 500x500 NonLinear, `.cardLogo`
|
|
188
|
+
* must take the 200x200 `<Companion>`.
|
|
189
|
+
* - **`exclude`** mirrors iOS's **greedy** `pool.select([hero, .cardLogo])`: roles resolve in array
|
|
190
|
+
* order and an asset a role claims is removed from the pool for every later role. We have no
|
|
191
|
+
* pool object, so the caller passes the assets the hero already claimed and they are filtered
|
|
192
|
+
* out here by `imageUrl`.
|
|
193
|
+
*/
|
|
194
|
+
const selectCardLogo = (ad, exclude) => {
|
|
195
|
+
const claimed = (exclude || []).map(item => item === null || item === void 0 ? void 0 : item.imageUrl).filter(url => !!url);
|
|
196
|
+
return rankBest(collectAssetsFrom(ad).filter(asset => asset.origin === 'companion' && claimed.indexOf(asset.imageUrl) === -1));
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
exports.collectAssets = collectAssets;
|
|
200
|
+
exports.selectCardLogo = selectCardLogo;
|
|
201
|
+
exports.selectSponsorLogo = selectSponsorLogo;
|