@streamlayer/feature-advertisement 1.0.0 → 1.1.1-rc.1342

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.
Files changed (68) hide show
  1. package/classic/package.json +5 -0
  2. package/lib/cjs/advertisement.d.ts +64 -0
  3. package/lib/cjs/advertisement.js +307 -0
  4. package/lib/cjs/index.d.ts +19 -0
  5. package/lib/cjs/index.js +39 -0
  6. package/lib/cjs/package.json +1 -0
  7. package/lib/cjs/units/external-ad.d.ts +58 -0
  8. package/lib/cjs/units/external-ad.js +390 -0
  9. package/lib/cjs/units/external-pause-ad.d.ts +50 -0
  10. package/lib/cjs/units/external-pause-ad.js +253 -0
  11. package/lib/cjs/units/gam.d.ts +16 -0
  12. package/lib/cjs/units/gam.js +104 -0
  13. package/lib/cjs/units/parse-vast-extensions.d.ts +17 -0
  14. package/lib/cjs/units/parse-vast-extensions.js +52 -0
  15. package/lib/cjs/units/pause-ad-card.d.ts +49 -0
  16. package/lib/cjs/units/pause-ad-card.js +136 -0
  17. package/lib/cjs/units/pause-ad.d.ts +16 -0
  18. package/lib/cjs/units/pause-ad.js +52 -0
  19. package/lib/cjs/units/ssai-ad-break.d.ts +21 -0
  20. package/lib/cjs/units/ssai-ad-break.js +11 -0
  21. package/lib/cjs/units/ssai-creative.d.ts +89 -0
  22. package/lib/cjs/units/ssai-creative.js +136 -0
  23. package/lib/cjs/units/ssai-detector.d.ts +180 -0
  24. package/lib/cjs/units/ssai-detector.js +1156 -0
  25. package/lib/cjs/units/ssai-preload.d.ts +20 -0
  26. package/lib/cjs/units/ssai-preload.js +62 -0
  27. package/lib/cjs/units/ssai-providers.d.ts +117 -0
  28. package/lib/cjs/units/ssai-providers.js +263 -0
  29. package/lib/cjs/units/vast-tracker.d.ts +45 -0
  30. package/lib/cjs/units/vast-tracker.js +64 -0
  31. package/lib/classic/_virtual/_rollupPluginBabelHelpers.js +71 -0
  32. package/lib/classic/advertisement.js +331 -0
  33. package/lib/classic/cjs/_virtual/_rollupPluginBabelHelpers.js +77 -0
  34. package/lib/classic/cjs/advertisement.js +333 -0
  35. package/lib/classic/cjs/index.js +47 -0
  36. package/lib/classic/cjs/package.json +1 -0
  37. package/lib/classic/cjs/units/external-ad.js +453 -0
  38. package/lib/classic/cjs/units/external-pause-ad.js +274 -0
  39. package/lib/classic/cjs/units/gam.js +110 -0
  40. package/lib/classic/cjs/units/parse-vast-extensions.js +56 -0
  41. package/lib/classic/cjs/units/pause-ad-card.js +134 -0
  42. package/lib/classic/cjs/units/pause-ad.js +72 -0
  43. package/lib/classic/cjs/units/ssai-ad-break.js +11 -0
  44. package/lib/classic/cjs/units/ssai-creative.js +142 -0
  45. package/lib/classic/cjs/units/ssai-detector.js +1170 -0
  46. package/lib/classic/cjs/units/ssai-preload.js +72 -0
  47. package/lib/classic/cjs/units/ssai-providers.js +299 -0
  48. package/lib/classic/cjs/units/vast-tracker.js +81 -0
  49. package/lib/classic/index.js +28 -0
  50. package/lib/classic/units/external-ad.js +447 -0
  51. package/lib/classic/units/external-pause-ad.js +271 -0
  52. package/lib/classic/units/gam.js +108 -0
  53. package/lib/classic/units/parse-vast-extensions.js +54 -0
  54. package/lib/classic/units/pause-ad-card.js +131 -0
  55. package/lib/classic/units/pause-ad.js +70 -0
  56. package/lib/classic/units/ssai-ad-break.js +9 -0
  57. package/lib/classic/units/ssai-creative.js +140 -0
  58. package/lib/classic/units/ssai-detector.js +1167 -0
  59. package/lib/classic/units/ssai-preload.js +68 -0
  60. package/lib/classic/units/ssai-providers.js +289 -0
  61. package/lib/classic/units/vast-tracker.js +77 -0
  62. package/lib/index.d.ts +2 -1
  63. package/lib/index.js +6 -0
  64. package/lib/units/pause-ad-card.d.ts +49 -0
  65. package/lib/units/pause-ad-card.js +130 -0
  66. package/lib/units/pause-ad.d.ts +1 -18
  67. package/lib/units/pause-ad.js +2 -49
  68. package/package.json +42 -9
@@ -0,0 +1,447 @@
1
+ import { asyncToGenerator as _asyncToGenerator } from '../_virtual/_rollupPluginBabelHelpers.js';
2
+ import 'core-js/modules/es.symbol.description.js';
3
+ import 'core-js/modules/es.error.cause.js';
4
+ import 'core-js/modules/es.array.iterator.js';
5
+ import 'core-js/modules/es.array.reduce.js';
6
+ import 'core-js/modules/es.array.sort.js';
7
+ import 'core-js/modules/es.json.stringify.js';
8
+ import 'core-js/modules/es.promise.js';
9
+ import 'core-js/modules/es.regexp.exec.js';
10
+ import 'core-js/modules/es.string.replace.js';
11
+ import 'core-js/modules/es.string.replace-all.js';
12
+ import 'core-js/modules/esnext.iterator.constructor.js';
13
+ import 'core-js/modules/esnext.iterator.filter.js';
14
+ import 'core-js/modules/esnext.iterator.find.js';
15
+ import 'core-js/modules/esnext.iterator.map.js';
16
+ import 'core-js/modules/esnext.iterator.reduce.js';
17
+ import 'core-js/modules/web.dom-collections.iterator.js';
18
+ import 'core-js/modules/web.url.js';
19
+ import 'core-js/modules/web.url.to-json.js';
20
+ import 'core-js/modules/web.url-search-params.js';
21
+ import 'core-js/modules/web.url-search-params.delete.js';
22
+ import 'core-js/modules/web.url-search-params.has.js';
23
+ import 'core-js/modules/web.url-search-params.size.js';
24
+ import { createLogger } from '@streamlayer/sdk-web-logger/classic';
25
+ import { VASTClient, VASTParser } from '@dailymotion/vast-client';
26
+ import '@streamlayer/sdk-web-api/classic';
27
+ import VMAP from 'vmap';
28
+ import { gam } from './gam.js';
29
+ import { parseVastExtension } from './parse-vast-extensions.js';
30
+
31
+ const log = createLogger('ui:gam-external-ad');
32
+ /** Default budget for a single VAST resolution before it is treated as "no ad". */
33
+ const VAST_FETCH_TIMEOUT_MS = 10000;
34
+ /**
35
+ * Resolve `promise`, but fall back to `undefined` if it doesn't settle within `ms`.
36
+ * A slow/unreachable VAST endpoint must degrade to "no ad" instead of leaving the
37
+ * fetcher (and the sidebar) stuck waiting. Uses a timer rather than `AbortController`
38
+ * so it stays safe on the ES5/Chrome-32 target.
39
+ */
40
+ const withTimeout = (promise, ms) => {
41
+ return Promise.race([promise, new Promise(resolve => {
42
+ setTimeout(() => resolve(undefined), ms);
43
+ })]);
44
+ };
45
+ // const preloadImage = (src: string) => {
46
+ // return new Promise((resolve, reject) => {
47
+ // const image = new Image()
48
+ // image.onload = () => resolve(src)
49
+ // image.onerror = () => reject()
50
+ // image.src = src
51
+ // })
52
+ // }
53
+ /**
54
+ * Get the first valid creative from an ad
55
+ */
56
+ const getCreative = ad => {
57
+ return ad.creatives.find(creative => creative.mediaFiles);
58
+ };
59
+ /**
60
+ * Get the URL of the first media file in the creative
61
+ */
62
+ const getMediaFileUrl = creative => {
63
+ var _mediaFiles$find;
64
+ const mediaFiles = creative === null || creative === void 0 ? void 0 : creative.mediaFiles;
65
+ if (!(mediaFiles !== null && mediaFiles !== void 0 && mediaFiles.length)) {
66
+ return '';
67
+ }
68
+ return ((_mediaFiles$find = mediaFiles.find(mediaFile => mediaFile.fileURL)) === null || _mediaFiles$find === void 0 ? void 0 : _mediaFiles$find.fileURL) || '';
69
+ };
70
+ /**
71
+ * Extract the linear creative's `<ClickThrough>` URL. Sources the sidebar CTA (QR code
72
+ * + click-through). `videoClickThroughURLTemplate` isn't strongly typed in the client lib.
73
+ */
74
+ const getClickThrough = creative => {
75
+ var _creative$videoClickT;
76
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
77
+ // @ts-ignore — vast-client types `videoClickThroughURLTemplate` loosely
78
+ return (creative === null || creative === void 0 || (_creative$videoClickT = creative.videoClickThroughURLTemplate) === null || _creative$videoClickT === void 0 ? void 0 : _creative$videoClickT.url) || undefined;
79
+ };
80
+ /**
81
+ * Extract every `<Companion>` variation of the first `<CompanionAds>` creative that carries a
82
+ * `<StaticResource>`, in document order. `width`/`height` are XML attributes (strings at
83
+ * runtime), normalised to numbers so the size mapping can compare them.
84
+ */
85
+ const getCompanions = ad => {
86
+ const creative = ad.creatives.find(item => item.type === 'companion');
87
+ return ((creative === null || creative === void 0 ? void 0 : creative.variations) || []).reduce((acc, variation) => {
88
+ var _variation$staticReso, _variation$companionC;
89
+ 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;
90
+ if (!imageUrl) {
91
+ return acc;
92
+ }
93
+ return acc.concat({
94
+ imageUrl,
95
+ clickUrl: (_variation$companionC = variation.companionClickThroughURLTemplate) !== null && _variation$companionC !== void 0 ? _variation$companionC : undefined,
96
+ width: Number(variation.width) || undefined,
97
+ height: Number(variation.height) || undefined,
98
+ creative,
99
+ variation
100
+ });
101
+ }, []);
102
+ };
103
+ /** Product companion mapping: the 300x75 companion is the logo, the banner-sized one the media. */
104
+ const LOGO_COMPANION_WIDTH = 300;
105
+ const LOGO_COMPANION_HEIGHT = 75;
106
+ const companionArea = companion => (companion.width || 0) * (companion.height || 0);
107
+ /**
108
+ * Split a `<CompanionAds>` creative's variations into the logo and media slots.
109
+ *
110
+ * The pair mapping activates only for a tag that ships an exact 300x75 companion next to at
111
+ * least one other. Deliberately conservative pending broader product sizes: on any other tag
112
+ * no companion is promoted to the logo slot and the first one stays the media/strip candidate.
113
+ */
114
+ const selectCompanions = companions => {
115
+ const logo = companions.length > 1 ? companions.find(companion => companion.width === LOGO_COMPANION_WIDTH && companion.height === LOGO_COMPANION_HEIGHT) : undefined;
116
+ if (!logo) {
117
+ return {
118
+ logo: undefined,
119
+ media: companions[0]
120
+ };
121
+ }
122
+ const media = companions.filter(companion => companion !== logo).sort((a, b) => companionArea(a) - companionArea(b)).pop();
123
+ return {
124
+ logo,
125
+ media
126
+ };
127
+ };
128
+ /**
129
+ * Extract the first `<NonLinearAds>` creative's static-image variation. Both the creative (which
130
+ * owns the `<TrackingEvents>`) and the variation (which owns the click-through) are kept —
131
+ * `VASTTracker` needs both.
132
+ *
133
+ * `@dailymotion/vast-client` 6.4.2 emits `nonlinearClickThroughURLTemplate` (lower-case `l`),
134
+ * which the `@types/vast-client` shim declares as `nonLinearClickThroughURLTemplate` — the
135
+ * runtime spelling is authoritative.
136
+ */
137
+ const getNonLinear = ad => {
138
+ var _creative$variations, _variation$nonlinearC;
139
+ const creative = ad.creatives.find(item => item.type === 'nonlinear');
140
+ const variation = creative === null || creative === void 0 || (_creative$variations = creative.variations) === null || _creative$variations === void 0 ? void 0 : _creative$variations[0];
141
+ const imageUrl = variation === null || variation === void 0 ? void 0 : variation.staticResource;
142
+ if (!creative || !variation || !imageUrl) {
143
+ return undefined;
144
+ }
145
+ return {
146
+ imageUrl,
147
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
148
+ // @ts-ignore — see the runtime-vs-shim spelling note above
149
+ clickUrl: (_variation$nonlinearC = variation.nonlinearClickThroughURLTemplate) !== null && _variation$nonlinearC !== void 0 ? _variation$nonlinearC : undefined,
150
+ width: Number(variation.width) || undefined,
151
+ height: Number(variation.height) || undefined,
152
+ creative,
153
+ variation
154
+ };
155
+ };
156
+ /**
157
+ * Map a parsed VAST response into our `AdData` shape. Shared by both acquisition
158
+ * paths — the GAM/VMAP tag flow and the Prebid auction flow — so the two always
159
+ * produce identical downstream data. Returns `undefined` when the response has no
160
+ * ad carrying creatives.
161
+ */
162
+ const buildAdData = (parsedVAST, order) => {
163
+ var _validAd$extensions$f;
164
+ const validAd = parsedVAST.ads.find(ads => ads.creatives.length > 0);
165
+ if (!validAd) {
166
+ log.warn({
167
+ order,
168
+ ads: parsedVAST.ads.length
169
+ }, 'vast response carries no ad with creatives — treated as no-ad');
170
+ return undefined;
171
+ }
172
+ const extension = (_validAd$extensions$f = validAd.extensions.find(ext => ext.attributes.type.startsWith('streamlayer'))) === null || _validAd$extensions$f === void 0 ? void 0 : _validAd$extensions$f.children[0];
173
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
174
+ // @ts-expect-error
175
+ const parsedExtension = parseVastExtension(extension);
176
+ const creative = getCreative(validAd);
177
+ const mediaFileUrl = getMediaFileUrl(creative);
178
+ const companions = getCompanions(validAd);
179
+ const {
180
+ logo,
181
+ media: mediaCompanion
182
+ } = selectCompanions(companions);
183
+ const nonLinear = getNonLinear(validAd);
184
+ const clickThrough = getClickThrough(creative) || (nonLinear === null || nonLinear === void 0 ? void 0 : nonLinear.clickUrl) || (mediaCompanion === null || mediaCompanion === void 0 ? void 0 : mediaCompanion.clickUrl);
185
+ log.info({
186
+ order,
187
+ adId: validAd.id,
188
+ title: validAd.title || undefined,
189
+ found: {
190
+ linear: !!creative,
191
+ mediaFile: mediaFileUrl || undefined,
192
+ nonLinear: nonLinear ? `${nonLinear.width}x${nonLinear.height}` : undefined,
193
+ companions: companions.map(item => `${item.width}x${item.height}`)
194
+ },
195
+ selected: {
196
+ logo: logo ? {
197
+ size: `${logo.width}x${logo.height}`,
198
+ url: logo.imageUrl
199
+ } : undefined,
200
+ media: mediaCompanion ? {
201
+ size: `${mediaCompanion.width}x${mediaCompanion.height}`,
202
+ url: mediaCompanion.imageUrl
203
+ } : undefined,
204
+ clickThrough: !!clickThrough
205
+ }
206
+ }, 'vast ad parsed');
207
+ return {
208
+ creative,
209
+ order,
210
+ mediaFileUrl,
211
+ companion: companions[0],
212
+ logo,
213
+ mediaCompanion,
214
+ nonLinear,
215
+ clickThrough,
216
+ title: validAd.title || undefined,
217
+ description: validAd.description || undefined,
218
+ ad: validAd,
219
+ extension: parsedExtension,
220
+ raw: parsedVAST
221
+ };
222
+ };
223
+ /** Path of the self-hosted Prebid Server OpenRTB auction endpoint. */
224
+ const PREBID_AUCTION_PATH = '/openrtb2/auction';
225
+ /**
226
+ * A Prebid link points at our Prebid Server auction endpoint (staging:
227
+ * `https://prebid.next.streamlayer.io/openrtb2/auction`) rather than at a VAST tag.
228
+ * Detected by path so both staging and production hosts match.
229
+ */
230
+ const isPrebidUrl = url => url.pathname.endsWith(PREBID_AUCTION_PATH);
231
+ /** Unique per-auction request id — Prebid responses are never cached/reused (RFC §5). */
232
+ const createRequestId = () => `sl-web-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
233
+ /**
234
+ * Build the OpenRTB request body for a single video auction. Placement config is
235
+ * carried on the Prebid link's query string so the consumer-facing interface stays a
236
+ * single URL string:
237
+ * - `configId` → `imp.ext.prebid.storedrequest.id` (production stored request).
238
+ * - `storedauctionresponse` → `imp.ext.prebid.storedauctionresponse.id` (staging mock).
239
+ * - `test=1` → top-level `test` flag.
240
+ * - `w` / `h` → video dimensions (default 1920×1080).
241
+ */
242
+ const buildAuctionRequest = url => {
243
+ var _ref, _params$get, _params$get2;
244
+ const params = url.searchParams;
245
+ const width = Number(params.get('w')) || 1920;
246
+ const height = Number(params.get('h')) || 1080;
247
+ const configId = (_ref = (_params$get = params.get('configId')) !== null && _params$get !== void 0 ? _params$get : params.get('config_id')) !== null && _ref !== void 0 ? _ref : undefined;
248
+ const storedAuctionResponse = (_params$get2 = params.get('storedauctionresponse')) !== null && _params$get2 !== void 0 ? _params$get2 : undefined;
249
+ const prebid = {};
250
+ if (storedAuctionResponse) {
251
+ prebid.storedauctionresponse = {
252
+ id: storedAuctionResponse
253
+ };
254
+ }
255
+ if (configId) {
256
+ prebid.storedrequest = {
257
+ id: configId
258
+ };
259
+ }
260
+ prebid.bidder = {
261
+ appnexus: {
262
+ placementId: 13144370
263
+ }
264
+ };
265
+ const request = {
266
+ id: createRequestId(),
267
+ imp: [{
268
+ id: '1',
269
+ video: {
270
+ mimes: ['video/mp4'],
271
+ protocols: [2, 3, 5, 6],
272
+ w: width,
273
+ h: height,
274
+ placement: 1,
275
+ linearity: 1
276
+ },
277
+ ext: {
278
+ prebid
279
+ }
280
+ }],
281
+ site: {
282
+ page: typeof location !== 'undefined' ? location.href : url.origin
283
+ },
284
+ tmax: 500
285
+ };
286
+ if (params.get('test') === '1') {
287
+ request.test = 1;
288
+ }
289
+ return request;
290
+ };
291
+ /**
292
+ * Run one Prebid Server auction and turn the winning inline VAST into an `AdStore`.
293
+ * Every bid's `adm` is inline VAST XML (no URL to fetch); bids are ordered by price
294
+ * so the winner plays first (RFC §2). A single bad bid must not drop the batch.
295
+ */
296
+ const fetchPrebidAds = /*#__PURE__*/function () {
297
+ var _ref2 = _asyncToGenerator(function* (prebidUrl) {
298
+ var _auction$seatbid, _params$get3, _params$get4;
299
+ const response = yield fetch(prebidUrl.toString(), {
300
+ method: 'POST',
301
+ headers: {
302
+ 'Content-Type': 'application/json'
303
+ },
304
+ body: JSON.stringify(buildAuctionRequest(prebidUrl))
305
+ });
306
+ if (!response.ok) {
307
+ throw new Error(`prebid auction failed with status ${response.status}`);
308
+ }
309
+ const auction = yield response.json();
310
+ const bids = ((_auction$seatbid = auction.seatbid) !== null && _auction$seatbid !== void 0 ? _auction$seatbid : []).reduce((acc, seat) => {
311
+ var _seat$bid;
312
+ return acc.concat((_seat$bid = seat.bid) !== null && _seat$bid !== void 0 ? _seat$bid : []);
313
+ }, []).filter(bid => !!bid.adm)
314
+ // Highest price wins — the winner is order 0 (RFC §2).
315
+ .sort((a, b) => {
316
+ var _b$price, _a$price;
317
+ return ((_b$price = b.price) !== null && _b$price !== void 0 ? _b$price : 0) - ((_a$price = a.price) !== null && _a$price !== void 0 ? _a$price : 0);
318
+ });
319
+ const parser = new DOMParser();
320
+ const loadedAds = yield Promise.all(bids.map((bid, i) => {
321
+ const xmlDoc = parser.parseFromString(bid.adm, 'text/xml');
322
+ // A fresh `VASTParser` per bid isolates parser state, mirroring the fresh
323
+ // `VASTClient` per link in the tag flow. `resolveAll: true` follows any
324
+ // `VASTAdTagURI` wrapper chain the bid's inline VAST may still contain.
325
+ return new VASTParser()
326
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
327
+ // @ts-ignore
328
+ .parseVAST(xmlDoc, {
329
+ resolveAll: true,
330
+ wrapperLimit: 10,
331
+ allowMultipleAds: true
332
+ }).then(parsedVAST => buildAdData(parsedVAST, i)).catch(err => {
333
+ // A single failing bid must not drop the whole batch.
334
+ log.error(err, 'failed to parse prebid VAST');
335
+ return undefined;
336
+ });
337
+ }));
338
+ const params = prebidUrl.searchParams;
339
+ const autoplay = ((_params$get3 = params.get('vpa')) === null || _params$get3 === void 0 ? void 0 : _params$get3.replaceAll('?', '')) !== 'click';
340
+ const muted = ((_params$get4 = params.get('vpmute')) === null || _params$get4 === void 0 ? void 0 : _params$get4.replaceAll('?', '')) === '1';
341
+ return {
342
+ ads: loadedAds.filter(d => !!d),
343
+ autoplay,
344
+ muted
345
+ };
346
+ });
347
+ return function fetchPrebidAds(_x) {
348
+ return _ref2.apply(this, arguments);
349
+ };
350
+ }();
351
+ const externalAd = instance => {
352
+ return gamUrl => instance.transport.nanoquery.createFetcherStore([gamUrl], {
353
+ dedupeTime: Infinity,
354
+ revalidateInterval: 0,
355
+ fetcher: function () {
356
+ var _ref3 = _asyncToGenerator(function* (adUrl) {
357
+ var _noncedLink$searchPar, _noncedLink$searchPar2, _noncedLink$searchPar3;
358
+ if (!adUrl || typeof adUrl !== 'string') {
359
+ throw new Error('ad url is empty');
360
+ }
361
+ const parsedAdUrl = (() => {
362
+ try {
363
+ return new URL(adUrl);
364
+ } catch (_unused) {
365
+ return undefined;
366
+ }
367
+ })();
368
+ // Prebid links hit our Prebid Server auction endpoint instead of a VAST tag.
369
+ // Run the OpenRTB auction and parse the winning bid's inline VAST (`adm`) into
370
+ // the same `AdStore` shape the GAM/VMAP path returns. See rfcs/prebid.
371
+ if (parsedAdUrl && isPrebidUrl(parsedAdUrl)) {
372
+ return fetchPrebidAds(parsedAdUrl);
373
+ }
374
+ const generateUrl = gam(adUrl);
375
+ const generatedUrl = yield generateUrl();
376
+ const noncedLink = generatedUrl === null || generatedUrl === void 0 ? void 0 : generatedUrl.url;
377
+ if (!noncedLink) {
378
+ throw new Error('ad url is not generated');
379
+ }
380
+ let links;
381
+ if (((_noncedLink$searchPar = noncedLink.searchParams.get('output')) === null || _noncedLink$searchPar === void 0 ? void 0 : _noncedLink$searchPar.replaceAll('?', '')) === 'vmap') {
382
+ const response = yield withTimeout(fetch(noncedLink.toString()), VAST_FETCH_TIMEOUT_MS);
383
+ if (!response) {
384
+ throw new Error('vmap fetch timed out');
385
+ }
386
+ const parser = new DOMParser();
387
+ const xmlStr = yield response.text();
388
+ const xmlDoc = parser.parseFromString(xmlStr, 'text/xml');
389
+ const vmap = new VMAP(xmlDoc);
390
+ links = vmap.adBreaks.map(ad => {
391
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
392
+ // @ts-expect-error
393
+ return ad.adSource.adTagURI.uri;
394
+ });
395
+ } else {
396
+ links = [noncedLink.toString()];
397
+ }
398
+ const loadedAds = yield Promise.all(links.map((link, i) => {
399
+ // `resolveAll: true` makes the client follow every `VASTAdTagURI` wrapper chain
400
+ // (up to the default depth of 10) and resolve all Ads, merging wrapper-level
401
+ // Impression/tracking URLs into the inline Ad. The default (`resolveAll: false`)
402
+ // only resolves the first standalone Ad and defers wrappers/remaining Ads to
403
+ // `getNextAds()`, which this fetcher never calls. A fresh `VASTClient` per link
404
+ // isolates parser state so concurrent (VMAP) resolutions don't clobber each other.
405
+ return withTimeout(new VASTClient()
406
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
407
+ // @ts-ignore
408
+ .get(link, {
409
+ resolveAll: true,
410
+ wrapperLimit: 10,
411
+ allowMultipleAds: true
412
+ }), VAST_FETCH_TIMEOUT_MS).then(parsedVAST => {
413
+ // `parsedVAST` is `undefined` when the resolution timed out (see `withTimeout`).
414
+ if (!parsedVAST) {
415
+ log.warn({
416
+ link,
417
+ timeoutMs: VAST_FETCH_TIMEOUT_MS
418
+ }, 'vast resolution timed out — treated as no-ad');
419
+ return undefined;
420
+ }
421
+ return buildAdData(parsedVAST, i);
422
+ }).catch(err => {
423
+ // A single failing wrapper chain must not drop the whole batch.
424
+ log.error(err, 'failed to resolve VAST');
425
+ return undefined;
426
+ });
427
+ }));
428
+ const autoplay = ((_noncedLink$searchPar2 = noncedLink.searchParams.get('vpa')) === null || _noncedLink$searchPar2 === void 0 ? void 0 : _noncedLink$searchPar2.replaceAll('?', '')) !== 'click';
429
+ const muted = ((_noncedLink$searchPar3 = noncedLink.searchParams.get('vpmute')) === null || _noncedLink$searchPar3 === void 0 ? void 0 : _noncedLink$searchPar3.replaceAll('?', '')) === '1';
430
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
431
+ // @ts-expect-error
432
+ const nonceManager = generatedUrl.manager;
433
+ return {
434
+ ads: loadedAds.filter(d => !!d),
435
+ autoplay,
436
+ muted,
437
+ nonceManager
438
+ };
439
+ });
440
+ return function fetcher(_x2) {
441
+ return _ref3.apply(this, arguments);
442
+ };
443
+ }()
444
+ });
445
+ };
446
+
447
+ export { externalAd };