@streamlayer/feature-advertisement 1.1.1-rc.1345 → 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.
Files changed (60) hide show
  1. package/lib/advertisement.js +24 -9
  2. package/lib/cjs/advertisement.js +24 -9
  3. package/lib/cjs/index.d.ts +2 -0
  4. package/lib/cjs/index.js +2 -0
  5. package/lib/cjs/units/ad-macros.d.ts +7 -0
  6. package/lib/cjs/units/ad-macros.js +91 -0
  7. package/lib/cjs/units/external-ad.d.ts +69 -0
  8. package/lib/cjs/units/external-ad.js +93 -62
  9. package/lib/cjs/units/external-pause-ad.js +7 -1
  10. package/lib/cjs/units/gam.d.ts +11 -1
  11. package/lib/cjs/units/gam.js +65 -17
  12. package/lib/cjs/units/parse-vast-extensions.d.ts +10 -1
  13. package/lib/cjs/units/parse-vast-extensions.js +5 -1
  14. package/lib/cjs/units/sponsor-logo.d.ts +78 -0
  15. package/lib/cjs/units/sponsor-logo.js +194 -0
  16. package/lib/cjs/units/ssai-detector.js +27 -11
  17. package/lib/cjs/units/ssai-log.d.ts +16 -0
  18. package/lib/cjs/units/ssai-log.js +23 -0
  19. package/lib/cjs/units/ssai-providers.js +48 -11
  20. package/lib/classic/advertisement.js +34 -17
  21. package/lib/classic/cjs/advertisement.js +34 -17
  22. package/lib/classic/cjs/index.js +2 -0
  23. package/lib/classic/cjs/units/ad-macros.js +115 -0
  24. package/lib/classic/cjs/units/external-ad.js +111 -64
  25. package/lib/classic/cjs/units/external-pause-ad.js +5 -1
  26. package/lib/classic/cjs/units/gam.js +73 -10
  27. package/lib/classic/cjs/units/parse-vast-extensions.js +5 -1
  28. package/lib/classic/cjs/units/sponsor-logo.js +201 -0
  29. package/lib/classic/cjs/units/ssai-detector.js +33 -8
  30. package/lib/classic/cjs/units/ssai-log.js +23 -0
  31. package/lib/classic/cjs/units/ssai-providers.js +86 -24
  32. package/lib/classic/index.js +1 -0
  33. package/lib/classic/units/ad-macros.js +113 -0
  34. package/lib/classic/units/external-ad.js +109 -65
  35. package/lib/classic/units/external-pause-ad.js +5 -1
  36. package/lib/classic/units/gam.js +73 -10
  37. package/lib/classic/units/parse-vast-extensions.js +5 -1
  38. package/lib/classic/units/sponsor-logo.js +197 -0
  39. package/lib/classic/units/ssai-detector.js +33 -8
  40. package/lib/classic/units/ssai-log.js +21 -0
  41. package/lib/classic/units/ssai-providers.js +86 -24
  42. package/lib/index.d.ts +2 -0
  43. package/lib/index.js +1 -0
  44. package/lib/units/ad-macros.d.ts +7 -0
  45. package/lib/units/ad-macros.js +87 -0
  46. package/lib/units/external-ad.d.ts +69 -0
  47. package/lib/units/external-ad.js +92 -64
  48. package/lib/units/external-pause-ad.js +7 -1
  49. package/lib/units/gam.d.ts +11 -1
  50. package/lib/units/gam.js +65 -17
  51. package/lib/units/parse-vast-extensions.d.ts +10 -1
  52. package/lib/units/parse-vast-extensions.js +5 -1
  53. package/lib/units/sponsor-logo.d.ts +78 -0
  54. package/lib/units/sponsor-logo.js +188 -0
  55. package/lib/units/ssai-detector.js +27 -11
  56. package/lib/units/ssai-log.d.ts +16 -0
  57. package/lib/units/ssai-log.js +18 -0
  58. package/lib/units/ssai-providers.js +48 -11
  59. package/lib/vast-client.d.ts +9 -0
  60. package/package.json +8 -8
@@ -2,9 +2,55 @@
2
2
 
3
3
  var sdkWebLogger = require('@streamlayer/sdk-web-logger');
4
4
  var vastClient = require('@dailymotion/vast-client');
5
+ var ssaiLog = require('./ssai-log.js');
5
6
 
6
7
  const log = sdkWebLogger.createLogger('ssai:provider');
7
8
  const DEFAULT_CREATIVE_API_BASE = 'https://hls.next.streamlayer.io/api/creatives/';
9
+ /**
10
+ * One creative-metadata round trip, on the narrative log: the request the SDK
11
+ * made and the body the ad service answered with, verbatim. This is the half of
12
+ * the story the manifest cannot tell — the ad's identity comes from the playout,
13
+ * its content comes from here.
14
+ */
15
+ const fetchCreativeJson = async (providerId, url, signal) => {
16
+ // `adApiBase` is customer infrastructure and may carry a key in its query;
17
+ // the request uses the URL as given, the log gets it with such values masked.
18
+ const shown = maskUrlSecrets(url);
19
+ ssaiLog.ssaiq.info({ provider: providerId, url: shown }, `[GET] → ${shown}`);
20
+ let response;
21
+ try {
22
+ response = await fetch(url, { signal });
23
+ }
24
+ catch (error) {
25
+ // An abort is the SDK cancelling its own request (break ended, creative
26
+ // changed) — a note, not a failure.
27
+ if (error?.name === 'AbortError') {
28
+ ssaiLog.ssaiq.debug({ provider: providerId, url: shown }, `[GET] ✗ aborted ${shown}`);
29
+ }
30
+ else {
31
+ ssaiLog.ssaiq.warn({ provider: providerId, url: shown, error: String(error) }, `[GET] ✗ ${shown}`);
32
+ }
33
+ throw error;
34
+ }
35
+ if (!response.ok) {
36
+ ssaiLog.ssaiq.warn({ provider: providerId, url: shown, status: response.status }, `[GET] ✗ ${response.status} ${shown}`);
37
+ throw new Error(`${providerId} creative request failed: ${response.status}`);
38
+ }
39
+ let raw;
40
+ try {
41
+ raw = (await response.json());
42
+ }
43
+ catch (error) {
44
+ // A 200 with a body that is not JSON would otherwise leave the log looking
45
+ // like a request still in flight.
46
+ ssaiLog.ssaiq.warn({ provider: providerId, url: shown, error: String(error) }, `[GET] ✗ ${response.status} ${shown} — not JSON`);
47
+ throw error;
48
+ }
49
+ ssaiLog.ssaiq.info({ provider: providerId, status: response.status, body: raw }, `[GET] ← ${response.status} ad metadata — ${raw?.advertiser || raw?.title || raw?.creativeId || 'ad'}`);
50
+ return raw;
51
+ };
52
+ /** `?apiKey=…` → `?apiKey=«redacted»`, for URLs that go on a screen. Regex, not `URL`: this ships to 2016 TV engines. */
53
+ const maskUrlSecrets = (url) => url.replace(/([?&][^=&]*(?:token|secret|password|key|authorization)[^=&]*=)[^&]*/gi, '$1«redacted»');
8
54
  /**
9
55
  * Shared low-level beacon helper. Prefers `navigator.sendBeacon` and falls back
10
56
  * to a no-cors `fetch`. Used by the default impression path and available to
@@ -103,12 +149,7 @@ const createStreamLayerProvider = (config = {}) => {
103
149
  return {
104
150
  id: 'streamlayer',
105
151
  resolve: async (creativeId, signal) => {
106
- const url = buildCreativeUrl(creativeApiBase, creativeId);
107
- const response = await fetch(url, { signal });
108
- if (!response.ok) {
109
- throw new Error(`creative request failed: ${response.status}`);
110
- }
111
- const raw = (await response.json());
152
+ const raw = await fetchCreativeJson('streamlayer', buildCreativeUrl(creativeApiBase, creativeId), signal);
112
153
  return mapApiResponse({ ...raw, creativeId: raw.creativeId || creativeId });
113
154
  },
114
155
  fireImpression: fireDefaultImpression,
@@ -152,11 +193,7 @@ const createQuortexProvider = (config = {}) => {
152
193
  if (!adApiBase) {
153
194
  throw new Error('quortex provider is missing `adApiBase`');
154
195
  }
155
- const response = await fetch(buildQuortexUrl(adApiBase, tenantName, adId), { signal });
156
- if (!response.ok) {
157
- throw new Error(`quortex creative request failed: ${response.status}`);
158
- }
159
- const raw = (await response.json());
196
+ const raw = await fetchCreativeJson('quortex', buildQuortexUrl(adApiBase, tenantName, adId), signal);
160
197
  return mapApiResponse({ ...raw, creativeId: raw.creativeId || adId });
161
198
  },
162
199
  fireImpression: fireDefaultImpression,
@@ -1,6 +1,7 @@
1
1
  import { defineProperty as _defineProperty, objectSpread2 as _objectSpread2 } from './_virtual/_rollupPluginBabelHelpers.js';
2
2
  import 'core-js/modules/es.array.iterator.js';
3
3
  import 'core-js/modules/esnext.iterator.constructor.js';
4
+ import 'core-js/modules/esnext.iterator.find.js';
4
5
  import 'core-js/modules/esnext.iterator.for-each.js';
5
6
  import 'core-js/modules/esnext.set.difference.v2.js';
6
7
  import 'core-js/modules/esnext.set.intersection.v2.js';
@@ -20,6 +21,7 @@ import { SsaiCreativeCache } from './units/ssai-creative.js';
20
21
  import { resolveProvider } from './units/ssai-providers.js';
21
22
  import { createSsaiDetector } from './units/ssai-detector.js';
22
23
  import { createSsaiAdBreakStore } from './units/ssai-ad-break.js';
24
+ import { ssaiq } from './units/ssai-log.js';
23
25
  import { preloadCreativeAssets } from './units/ssai-preload.js';
24
26
 
25
27
  class Advertisement extends AbstractFeatureUnit {
@@ -165,10 +167,33 @@ class Advertisement extends AbstractFeatureUnit {
165
167
  const provider = resolveProvider(options.provider);
166
168
  const cache = new SsaiCreativeCache(provider);
167
169
  this.ssaiCache = cache;
170
+ /**
171
+ * Every beacon the SDK fires gets a line, with the URL it was fired at.
172
+ * The narrative log is read as a live trace of one ad, and "we called the
173
+ * ad server's own tracking URL" is as much of the story as the request
174
+ * that fetched the creative in the first place.
175
+ */
176
+ const logBeacon = (creativeId, event) => {
177
+ var _cache$peek;
178
+ const url = (_cache$peek = cache.peek(creativeId)) === null || _cache$peek === void 0 || (_cache$peek = _cache$peek.trackingEvents) === null || _cache$peek === void 0 || (_cache$peek = _cache$peek.find(entry => entry.event === event)) === null || _cache$peek === void 0 ? void 0 : _cache$peek.url;
179
+ ssaiq.info({
180
+ creativeId,
181
+ event,
182
+ url
183
+ }, `[beacon] ${event} fired — ${creativeId}`);
184
+ };
168
185
  this.fireSsaiImpression = creativeId => {
186
+ var _cache$peek2;
187
+ // The UI only calls this once the bar is actually rendered, so this is
188
+ // the moment the viewer saw the ad — not the moment it resolved.
189
+ ssaiq.info({
190
+ creativeId
191
+ }, `[ad] ad on screen — ${((_cache$peek2 = cache.peek(creativeId)) === null || _cache$peek2 === void 0 ? void 0 : _cache$peek2.advertiser) || creativeId}`);
192
+ logBeacon(creativeId, 'impression');
169
193
  cache.fireImpressionById(creativeId);
170
194
  };
171
195
  this.fireSsaiTracking = (creativeId, event) => {
196
+ logBeacon(creativeId, event);
172
197
  cache.fireTrackingById(creativeId, event);
173
198
  };
174
199
  this.fireSsaiCompanionClick = creativeId => {
@@ -186,10 +211,14 @@ class Advertisement extends AbstractFeatureUnit {
186
211
  * when the ad actually starts.
187
212
  */
188
213
  const prefetchCreative = creativeId => {
189
- cache.resolve(creativeId).then(creative => preloadCreativeAssets(creative)).then(() => {
214
+ cache.resolve(creativeId).then(creative => preloadCreativeAssets(creative).then(() => creative)).then(creative => {
190
215
  preloadedIds.add(creativeId);
191
- }).catch(error => {
192
- this.log.debug(error, 'ssai creative prefetch failed');
216
+ ssaiq.info({
217
+ creativeId
218
+ }, `[ad] creative ready — ${creative.advertiser} (artwork preloaded)`);
219
+ }).catch(() => {
220
+ // Already on `ssaiq` from the provider; the break may still be
221
+ // minutes away, and `loadCreative` retries when it actually starts.
193
222
  });
194
223
  };
195
224
  const publishReady = (range, creative) => {
@@ -251,15 +280,9 @@ class Advertisement extends AbstractFeatureUnit {
251
280
  videoRef: options.videoRef,
252
281
  manifestUrl: options.manifestUrl,
253
282
  onAdDiscovered: creativeId => {
254
- this.log.debug({
255
- creativeId
256
- }, 'ssai ad discovered, prefetching creative');
257
283
  prefetchCreative(creativeId);
258
284
  },
259
285
  onBreakStart: range => {
260
- this.log.debug({
261
- creativeId: range.creativeId
262
- }, 'ssai break start');
263
286
  eventBus.emit('ssaiAdBreak', {
264
287
  action: 'breakStart',
265
288
  payload: {
@@ -269,9 +292,6 @@ class Advertisement extends AbstractFeatureUnit {
269
292
  loadCreative(range);
270
293
  },
271
294
  onCreativeChange: range => {
272
- this.log.debug({
273
- creativeId: range.creativeId
274
- }, 'ssai creative change');
275
295
  loadCreative(range);
276
296
  },
277
297
  onAdEnding: range => {
@@ -286,14 +306,11 @@ class Advertisement extends AbstractFeatureUnit {
286
306
  }));
287
307
  },
288
308
  onProgress: (range, event) => {
289
- this.log.debug({
290
- creativeId: range.creativeId,
291
- event
292
- }, 'ssai progress');
309
+ // Logged by the detector as `progress:<event>` at the moment playback
310
+ // crosses the marker; nothing to add here.
293
311
  cache.fireTrackingById(range.creativeId, event);
294
312
  },
295
313
  onBreakEnd: () => {
296
- this.log.debug('ssai break end');
297
314
  const current = this.ssaiAdBreak.get();
298
315
  eventBus.emit('ssaiAdBreak', {
299
316
  action: 'breakEnd',
@@ -3,6 +3,7 @@
3
3
  var _rollupPluginBabelHelpers = require('./_virtual/_rollupPluginBabelHelpers.js');
4
4
  require('core-js/modules/es.array.iterator.js');
5
5
  require('core-js/modules/esnext.iterator.constructor.js');
6
+ require('core-js/modules/esnext.iterator.find.js');
6
7
  require('core-js/modules/esnext.iterator.for-each.js');
7
8
  require('core-js/modules/esnext.set.difference.v2.js');
8
9
  require('core-js/modules/esnext.set.intersection.v2.js');
@@ -22,6 +23,7 @@ var ssaiCreative = require('./units/ssai-creative.js');
22
23
  var ssaiProviders = require('./units/ssai-providers.js');
23
24
  var ssaiDetector = require('./units/ssai-detector.js');
24
25
  var ssaiAdBreak = require('./units/ssai-ad-break.js');
26
+ var ssaiLog = require('./units/ssai-log.js');
25
27
  var ssaiPreload = require('./units/ssai-preload.js');
26
28
 
27
29
  class Advertisement extends classic.AbstractFeatureUnit {
@@ -167,10 +169,33 @@ class Advertisement extends classic.AbstractFeatureUnit {
167
169
  const provider = ssaiProviders.resolveProvider(options.provider);
168
170
  const cache = new ssaiCreative.SsaiCreativeCache(provider);
169
171
  this.ssaiCache = cache;
172
+ /**
173
+ * Every beacon the SDK fires gets a line, with the URL it was fired at.
174
+ * The narrative log is read as a live trace of one ad, and "we called the
175
+ * ad server's own tracking URL" is as much of the story as the request
176
+ * that fetched the creative in the first place.
177
+ */
178
+ const logBeacon = (creativeId, event) => {
179
+ var _cache$peek;
180
+ const url = (_cache$peek = cache.peek(creativeId)) === null || _cache$peek === void 0 || (_cache$peek = _cache$peek.trackingEvents) === null || _cache$peek === void 0 || (_cache$peek = _cache$peek.find(entry => entry.event === event)) === null || _cache$peek === void 0 ? void 0 : _cache$peek.url;
181
+ ssaiLog.ssaiq.info({
182
+ creativeId,
183
+ event,
184
+ url
185
+ }, `[beacon] ${event} fired — ${creativeId}`);
186
+ };
170
187
  this.fireSsaiImpression = creativeId => {
188
+ var _cache$peek2;
189
+ // The UI only calls this once the bar is actually rendered, so this is
190
+ // the moment the viewer saw the ad — not the moment it resolved.
191
+ ssaiLog.ssaiq.info({
192
+ creativeId
193
+ }, `[ad] ad on screen — ${((_cache$peek2 = cache.peek(creativeId)) === null || _cache$peek2 === void 0 ? void 0 : _cache$peek2.advertiser) || creativeId}`);
194
+ logBeacon(creativeId, 'impression');
171
195
  cache.fireImpressionById(creativeId);
172
196
  };
173
197
  this.fireSsaiTracking = (creativeId, event) => {
198
+ logBeacon(creativeId, event);
174
199
  cache.fireTrackingById(creativeId, event);
175
200
  };
176
201
  this.fireSsaiCompanionClick = creativeId => {
@@ -188,10 +213,14 @@ class Advertisement extends classic.AbstractFeatureUnit {
188
213
  * when the ad actually starts.
189
214
  */
190
215
  const prefetchCreative = creativeId => {
191
- cache.resolve(creativeId).then(creative => ssaiPreload.preloadCreativeAssets(creative)).then(() => {
216
+ cache.resolve(creativeId).then(creative => ssaiPreload.preloadCreativeAssets(creative).then(() => creative)).then(creative => {
192
217
  preloadedIds.add(creativeId);
193
- }).catch(error => {
194
- this.log.debug(error, 'ssai creative prefetch failed');
218
+ ssaiLog.ssaiq.info({
219
+ creativeId
220
+ }, `[ad] creative ready — ${creative.advertiser} (artwork preloaded)`);
221
+ }).catch(() => {
222
+ // Already on `ssaiq` from the provider; the break may still be
223
+ // minutes away, and `loadCreative` retries when it actually starts.
195
224
  });
196
225
  };
197
226
  const publishReady = (range, creative) => {
@@ -253,15 +282,9 @@ class Advertisement extends classic.AbstractFeatureUnit {
253
282
  videoRef: options.videoRef,
254
283
  manifestUrl: options.manifestUrl,
255
284
  onAdDiscovered: creativeId => {
256
- this.log.debug({
257
- creativeId
258
- }, 'ssai ad discovered, prefetching creative');
259
285
  prefetchCreative(creativeId);
260
286
  },
261
287
  onBreakStart: range => {
262
- this.log.debug({
263
- creativeId: range.creativeId
264
- }, 'ssai break start');
265
288
  classic.eventBus.emit('ssaiAdBreak', {
266
289
  action: 'breakStart',
267
290
  payload: {
@@ -271,9 +294,6 @@ class Advertisement extends classic.AbstractFeatureUnit {
271
294
  loadCreative(range);
272
295
  },
273
296
  onCreativeChange: range => {
274
- this.log.debug({
275
- creativeId: range.creativeId
276
- }, 'ssai creative change');
277
297
  loadCreative(range);
278
298
  },
279
299
  onAdEnding: range => {
@@ -288,14 +308,11 @@ class Advertisement extends classic.AbstractFeatureUnit {
288
308
  }));
289
309
  },
290
310
  onProgress: (range, event) => {
291
- this.log.debug({
292
- creativeId: range.creativeId,
293
- event
294
- }, 'ssai progress');
311
+ // Logged by the detector as `progress:<event>` at the moment playback
312
+ // crosses the marker; nothing to add here.
295
313
  cache.fireTrackingById(range.creativeId, event);
296
314
  },
297
315
  onBreakEnd: () => {
298
- this.log.debug('ssai break end');
299
316
  const current = this.ssaiAdBreak.get();
300
317
  classic.eventBus.emit('ssaiAdBreak', {
301
318
  action: 'breakEnd',
@@ -3,6 +3,7 @@
3
3
  var advertisement$1 = require('./advertisement.js');
4
4
  var externalPauseAd = require('./units/external-pause-ad.js');
5
5
  var pauseAdCard = require('./units/pause-ad-card.js');
6
+ var sponsorLogo = require('./units/sponsor-logo.js');
6
7
  var gam = require('./units/gam.js');
7
8
  var vastTracker = require('./units/vast-tracker.js');
8
9
  require('core-js/modules/es.array.iterator.js');
@@ -29,6 +30,7 @@ const advertisement = (instance, _opts, done) => {
29
30
 
30
31
  exports.isFullbleedVideoTemplate = externalPauseAd.isFullbleedVideoTemplate;
31
32
  exports.INSIGHT_BODY_MAX_LENGTH = pauseAdCard.INSIGHT_BODY_MAX_LENGTH;
33
+ exports.selectSponsorLogo = sponsorLogo.selectSponsorLogo;
32
34
  exports.gam = gam.gam;
33
35
  exports.createCompanionTracker = vastTracker.createCompanionTracker;
34
36
  exports.createVastTracker = vastTracker.createVastTracker;
@@ -0,0 +1,115 @@
1
+ 'use strict';
2
+
3
+ require('core-js/modules/es.regexp.exec.js');
4
+ require('core-js/modules/es.string.replace.js');
5
+
6
+ /**
7
+ * Ad-tag macro substitution, for every ad server — not just GAM.
8
+ *
9
+ * Studio stores a programmatic tag verbatim (`gamRawUrl`, returned untouched by
10
+ * `processGamAdvertisement`), so whatever macros the ad server's own tag builder emitted reach the
11
+ * SDK literal. `gam()` used to fill only the GAM `[placeholder]` token, and only for
12
+ * `pubads.g.doubleclick.net` — every other vendor's tag went out with its macros unexpanded.
13
+ *
14
+ * That is not inert. Measured against the ADITION tag on event 11089
15
+ * (`…/banner?…&ts=[timestamp]&clickurl=[clickmacro]`): ADITION prepends the value of `clickurl` to
16
+ * its own redi URL, so a literal `[clickmacro]` came back inside `<CompanionClickThrough>` as
17
+ * `[clickmacro]https%3A%2F%2Fad1…` — a broken click-through. Sending `clickurl=` empty answers with
18
+ * a valid redi URL.
19
+ *
20
+ * **Two sources.** The cache buster is the SDK's own. The consent family — `${GDPR}` and
21
+ * `${GDPR_CONSENT_<gvlid>}`, IAB TCF, where the id is the vendor's Global Vendor List entry (39 is
22
+ * ADITION / Virtual Minds) — and the click macro come from the host app (`AdTagParams`, set through
23
+ * the `adTagParams` option or `sdk.setAdTagParams`). The SDK never reads a CMP and never guesses
24
+ * consent: a value the host did not pass goes out EMPTY, which ad servers read as "unknown" — never
25
+ * as `gdpr=0`. Empty rather than literal because a literal is not inert either: ADITION copied
26
+ * `${GDPR}` into its own redirect URL (`gdpr%3D%24%7BGDPR%7D`). See
27
+ * `docs/wiki/programmatic-ads-macros.md`.
28
+ *
29
+ * An unknown macro is left exactly as configured, which is what keeps the GAM `[placeholder]`
30
+ * tokens for the PAL path (`gam.ts` fills those per parameter, not per token).
31
+ */
32
+ /**
33
+ * All three spellings in the wild — `[NAME]` (IAB / SpotX), `${NAME}` (Google) and bare `{NAME}` —
34
+ * each with its delimiters either literal or PERCENT-ENCODED.
35
+ *
36
+ * Neither half of that is theoretical. ADITION emitted `[timestamp]` / `[clickmacro]` for the
37
+ * campaign on event 11089 and `{CACHEBUSTER}` / `{GDPR}` for the one on 6203 — same ad server, same
38
+ * advertiser, different tag builder. And Studio stores the 6203 tag as `ts=%7BCACHEBUSTER%7D`, so
39
+ * matching only literal braces made the whole substitution a silent no-op on it: an unexpanded
40
+ * macro raises no error and the ad server still answers 200. `searchParams.get()` decodes on read,
41
+ * which is what hides the encoding from anyone inspecting the parsed URL.
42
+ *
43
+ * The `$` is optional rather than a fourth branch — an unowned `${NAME}` still returns its full
44
+ * matched text, `$` included, so the brace forms cannot corrupt each other.
45
+ */
46
+ const MACRO_PATTERN = /(?:\[|%5B)([a-z0-9_]+)(?:\]|%5D)|(?:\$|%24)?(?:\{|%7B)([a-z0-9_]+)(?:\}|%7D)/gi;
47
+ /**
48
+ * Every host value lands inside a query-string VALUE, so it is encoded exactly once. That matters
49
+ * most for the click URL, which carries its own `?`/`&`: sent raw it re-opens the tag's query, the
50
+ * way the malformed 6203 tag did (`clickurl` truncated, every following parameter duplicated).
51
+ */
52
+ const hostValue = value => value ? encodeURIComponent(value) : '';
53
+ const gdprConsent = ({
54
+ params
55
+ }) => hostValue(params === null || params === void 0 ? void 0 : params.tcString);
56
+ /**
57
+ * Macro name (lower-cased) → the value this SDK sends for it.
58
+ *
59
+ * `clickurl` / `clickmacro` take the host's click tracker, which the ad server PREPENDS to its own
60
+ * redirect — so the viewer's click passes host tracker → vendor redirect → landing page. The SDK
61
+ * does not fire that tracker itself; it would count the click twice. Without one it is empty, the
62
+ * documented "no player tracker" value. VAST click beacons are reported separately by
63
+ * `useSidebarVastTracker`.
64
+ */
65
+ const MACRO_VALUES = {
66
+ // One family, one value: every name here means "make this request unique so nothing caches it".
67
+ // Epoch milliseconds satisfies that for all of them, and keeps the value already shipped for
68
+ // `timestamp` unchanged. (IAB defines `[CACHEBUSTING]` as a random 8-digit number and
69
+ // `[TIMESTAMP]` as ISO-8601, but those describe VAST *tracking* URLs; this substitution only
70
+ // ever touches the ad REQUEST url, where every ad server documents the parameter as a number.)
71
+ timestamp: ({
72
+ now
73
+ }) => String(now),
74
+ cachebuster: ({
75
+ now
76
+ }) => String(now),
77
+ cachebusting: ({
78
+ now
79
+ }) => String(now),
80
+ clickmacro: ({
81
+ params
82
+ }) => hostValue(params === null || params === void 0 ? void 0 : params.clickUrl),
83
+ clickurl: ({
84
+ params
85
+ }) => hostValue(params === null || params === void 0 ? void 0 : params.clickUrl),
86
+ // `1` / `0` only from an explicit boolean — an absent value is unknown, not "does not apply".
87
+ gdpr: ({
88
+ params
89
+ }) => (params === null || params === void 0 ? void 0 : params.gdprApplies) === undefined ? '' : params.gdprApplies ? '1' : '0',
90
+ gdpr_consent: gdprConsent
91
+ };
92
+ /** `GDPR_CONSENT_<gvlid>`: the vendor id names whose tag this is; the value is the same TC string. */
93
+ const GDPR_CONSENT_FOR_VENDOR = /^gdpr_consent_\d+$/;
94
+ const resolveMacro = name => {
95
+ var _MACRO_VALUES$name;
96
+ return (_MACRO_VALUES$name = MACRO_VALUES[name]) !== null && _MACRO_VALUES$name !== void 0 ? _MACRO_VALUES$name : GDPR_CONSENT_FOR_VENDOR.test(name) ? gdprConsent : undefined;
97
+ };
98
+ /**
99
+ * Fill the macros this SDK knows in an ad-tag URL. One timestamp per call, so a tag naming the macro
100
+ * twice stays self-consistent. `params` is read as given — the caller passes the host's CURRENT
101
+ * values at request time.
102
+ */
103
+ const substituteAdTagMacros = (url, params) => {
104
+ const now = Date.now();
105
+ return url.replace(MACRO_PATTERN, (match, bracketed, braced) => {
106
+ var _ref;
107
+ const value = resolveMacro(((_ref = bracketed !== null && bracketed !== void 0 ? bracketed : braced) !== null && _ref !== void 0 ? _ref : '').toLowerCase());
108
+ return value ? value({
109
+ now,
110
+ params
111
+ }) : match;
112
+ });
113
+ };
114
+
115
+ exports.substituteAdTagMacros = substituteAdTagMacros;