@streamlayer/feature-advertisement 1.1.1-rc.1350 → 1.1.1-rc.3.pda7fd93b

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
@@ -23,6 +23,7 @@ require('core-js/modules/esnext.set.symmetric-difference.v2.js');
23
23
  require('core-js/modules/esnext.set.union.v2.js');
24
24
  require('core-js/modules/web.dom-collections.iterator.js');
25
25
  var classic = require('@streamlayer/sdk-web-logger/classic');
26
+ var ssaiLog = require('./ssai-log.js');
26
27
 
27
28
  const log = classic.createLogger('ssai:detector');
28
29
  const RANGE_EXPIRY_MS = 10 * 60 * 1000;
@@ -536,16 +537,37 @@ const createSsaiDetector = options => {
536
537
  // Malformed tags persist in the manifest across refreshes; log each once.
537
538
  const seenDroppedKeys = new Set();
538
539
  const attachMode = player ? 'hls' : videoRef && manifestUrl ? 'manifest-poll' : 'none';
539
- const recordDebugEvent = (type, detail, programMs) => {
540
+ /**
541
+ * The `[label]` a detector event's log line opens with — what KIND of thing
542
+ * happened, for a reader scanning a window: a manifest tag came in (`hls`), a
543
+ * break edge fired (`break`), or playback crossed a tracking marker (`beacon`).
544
+ */
545
+ const eventLabel = type => {
546
+ if (type.indexOf('progress:') === 0) return 'beacon';
547
+ if (type.indexOf('discovered') !== -1 || type === 'range-dropped') return 'hls';
548
+ return 'break';
549
+ };
550
+ /**
551
+ * One call per detector event, feeding both consumers at once: the capped
552
+ * ring buffer behind `getDebugSnapshot()` and the `ssaiq` narrative logger.
553
+ * `data` is whatever the detector already holds for the event — the parsed
554
+ * range, marker or break window — so a log reader sees exactly what the
555
+ * detector acted on, with no second parse of the manifest for its benefit.
556
+ */
557
+ const recordDebugEvent = (type, detail, programMs, data) => {
558
+ const at = programMs !== undefined ? programMs : currentProgramTimeMs();
540
559
  debugEvents.push({
541
560
  wallMs: Date.now(),
542
- programMs: programMs !== undefined ? programMs : currentProgramTimeMs(),
561
+ programMs: at,
543
562
  type,
544
563
  detail
545
564
  });
546
565
  if (debugEvents.length > DEBUG_EVENTS_MAX) {
547
566
  debugEvents.splice(0, debugEvents.length - DEBUG_EVENTS_MAX);
548
567
  }
568
+ ssaiLog.ssaiq.info(_rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, data), {}, {
569
+ programMs: at
570
+ }), `[${eventLabel(type)}] ${type}${detail ? ` ${detail}` : ''}`);
549
571
  };
550
572
  const getVideo = () => {
551
573
  var _ref, _player$media2;
@@ -598,9 +620,6 @@ const createSsaiDetector = options => {
598
620
  }
599
621
  seenDroppedKeys.add(entry.key);
600
622
  recordDebugEvent('range-dropped', entry.detail);
601
- log.warn({
602
- detail: entry.detail
603
- }, 'ssai DATERANGE dropped');
604
623
  }
605
624
  continue;
606
625
  }
@@ -608,7 +627,9 @@ const createSsaiDetector = options => {
608
627
  const existing = rangesById.get(entry.range.id);
609
628
  if (!existing) {
610
629
  changed = true;
611
- recordDebugEvent('range-discovered', `${entry.range.creativeId} ${debugClock(entry.range.startMs)}+${entry.range.durationSec}s`);
630
+ recordDebugEvent('range-discovered', `${entry.range.creativeId} ${debugClock(entry.range.startMs)}+${entry.range.durationSec}s`, undefined, {
631
+ range: entry.range
632
+ });
612
633
  notifyAdDiscovered(entry.range.creativeId);
613
634
  } else if (existing.startMs !== entry.range.startMs || existing.endMs !== entry.range.endMs) {
614
635
  changed = true;
@@ -617,7 +638,9 @@ const createSsaiDetector = options => {
617
638
  } else if (entry.kind === 'marker') {
618
639
  if (!seenMarkerAdIds.has(entry.marker.adId)) {
619
640
  seenMarkerAdIds.add(entry.marker.adId);
620
- recordDebugEvent('marker-ad-discovered', `${entry.marker.adId} ${entry.marker.event}@${debugClock(entry.marker.timeMs)}`);
641
+ recordDebugEvent('marker-ad-discovered', `${entry.marker.adId} ${entry.marker.event}@${debugClock(entry.marker.timeMs)}`, undefined, {
642
+ marker: entry.marker
643
+ });
621
644
  notifyAdDiscovered(entry.marker.adId);
622
645
  }
623
646
  const existing = markersById.get(entry.marker.id);
@@ -629,7 +652,9 @@ const createSsaiDetector = options => {
629
652
  const existing = adBreaksById.get(entry.window.id);
630
653
  if (!existing) {
631
654
  changed = true;
632
- recordDebugEvent('adbreak-discovered', `${debugClock(entry.window.startMs)}-${debugClock(entry.window.endMs)}`);
655
+ recordDebugEvent('adbreak-discovered', `${debugClock(entry.window.startMs)}-${debugClock(entry.window.endMs)}`, undefined, {
656
+ window: entry.window
657
+ });
633
658
  } else if (existing.startMs !== entry.window.startMs || existing.endMs !== entry.window.endMs) {
634
659
  changed = true;
635
660
  }
@@ -0,0 +1,23 @@
1
+ 'use strict';
2
+
3
+ var classic = require('@streamlayer/sdk-web-logger/classic');
4
+
5
+ /**
6
+ * `ssaiq` — the SSAI ad NARRATIVE log: one line per thing that happens to a
7
+ * server-stitched ad, in the order it happens. The manifest tag the playout
8
+ * published, the metadata round trip it triggers, the break the SDK opens from
9
+ * it, and every beacon fired against it.
10
+ *
11
+ * Named for the Quortex/Synamedia DAI integration it was built for, but it is
12
+ * the path itself that is logged, so a StreamLayer-dialect stream tells the same
13
+ * story. Detector internals — clock sources, poll ticks, fold bookkeeping — stay
14
+ * on `ssai:*`, where `SL_DEBUG` can reach them without filling a demo window.
15
+ *
16
+ * A host publishes it to the page with `SL_LOG_EVENTS=ssaiq` (see
17
+ * `sdk-web-logger`); the demo's log window renders exactly that.
18
+ */
19
+ // Explicit type: the inferred pino type is not nameable across package
20
+ // boundaries (TS2742) when this file is compiled on its own.
21
+ const ssaiq = classic.createLogger('ssaiq');
22
+
23
+ exports.ssaiq = ssaiq;
@@ -4,6 +4,8 @@ var _rollupPluginBabelHelpers = require('../_virtual/_rollupPluginBabelHelpers.j
4
4
  require('core-js/modules/es.error.cause.js');
5
5
  require('core-js/modules/es.array.iterator.js');
6
6
  require('core-js/modules/es.promise.js');
7
+ require('core-js/modules/es.regexp.exec.js');
8
+ require('core-js/modules/es.string.replace.js');
7
9
  require('core-js/modules/esnext.iterator.constructor.js');
8
10
  require('core-js/modules/esnext.iterator.filter.js');
9
11
  require('core-js/modules/esnext.iterator.find.js');
@@ -12,9 +14,82 @@ require('core-js/modules/web.dom-collections.iterator.js');
12
14
  require('core-js/modules/web.dom-exception.stack.js');
13
15
  var classic = require('@streamlayer/sdk-web-logger/classic');
14
16
  var vastClient = require('@dailymotion/vast-client');
17
+ var ssaiLog = require('./ssai-log.js');
15
18
 
16
19
  const log = classic.createLogger('ssai:provider');
17
20
  const DEFAULT_CREATIVE_API_BASE = 'https://hls.next.streamlayer.io/api/creatives/';
21
+ /**
22
+ * One creative-metadata round trip, on the narrative log: the request the SDK
23
+ * made and the body the ad service answered with, verbatim. This is the half of
24
+ * the story the manifest cannot tell — the ad's identity comes from the playout,
25
+ * its content comes from here.
26
+ */
27
+ const fetchCreativeJson = /*#__PURE__*/function () {
28
+ var _ref = _rollupPluginBabelHelpers.asyncToGenerator(function* (providerId, url, signal) {
29
+ var _raw, _raw2, _raw3;
30
+ // `adApiBase` is customer infrastructure and may carry a key in its query;
31
+ // the request uses the URL as given, the log gets it with such values masked.
32
+ const shown = maskUrlSecrets(url);
33
+ ssaiLog.ssaiq.info({
34
+ provider: providerId,
35
+ url: shown
36
+ }, `[GET] → ${shown}`);
37
+ let response;
38
+ try {
39
+ response = yield fetch(url, {
40
+ signal
41
+ });
42
+ } catch (error) {
43
+ // An abort is the SDK cancelling its own request (break ended, creative
44
+ // changed) — a note, not a failure.
45
+ if ((error === null || error === void 0 ? void 0 : error.name) === 'AbortError') {
46
+ ssaiLog.ssaiq.debug({
47
+ provider: providerId,
48
+ url: shown
49
+ }, `[GET] ✗ aborted ${shown}`);
50
+ } else {
51
+ ssaiLog.ssaiq.warn({
52
+ provider: providerId,
53
+ url: shown,
54
+ error: String(error)
55
+ }, `[GET] ✗ ${shown}`);
56
+ }
57
+ throw error;
58
+ }
59
+ if (!response.ok) {
60
+ ssaiLog.ssaiq.warn({
61
+ provider: providerId,
62
+ url: shown,
63
+ status: response.status
64
+ }, `[GET] ✗ ${response.status} ${shown}`);
65
+ throw new Error(`${providerId} creative request failed: ${response.status}`);
66
+ }
67
+ let raw;
68
+ try {
69
+ raw = yield response.json();
70
+ } catch (error) {
71
+ // A 200 with a body that is not JSON would otherwise leave the log looking
72
+ // like a request still in flight.
73
+ ssaiLog.ssaiq.warn({
74
+ provider: providerId,
75
+ url: shown,
76
+ error: String(error)
77
+ }, `[GET] ✗ ${response.status} ${shown} — not JSON`);
78
+ throw error;
79
+ }
80
+ ssaiLog.ssaiq.info({
81
+ provider: providerId,
82
+ status: response.status,
83
+ body: raw
84
+ }, `[GET] ← ${response.status} ad metadata — ${((_raw = raw) === null || _raw === void 0 ? void 0 : _raw.advertiser) || ((_raw2 = raw) === null || _raw2 === void 0 ? void 0 : _raw2.title) || ((_raw3 = raw) === null || _raw3 === void 0 ? void 0 : _raw3.creativeId) || 'ad'}`);
85
+ return raw;
86
+ });
87
+ return function fetchCreativeJson(_x, _x2, _x3) {
88
+ return _ref.apply(this, arguments);
89
+ };
90
+ }();
91
+ /** `?apiKey=…` → `?apiKey=«redacted»`, for URLs that go on a screen. Regex, not `URL`: this ships to 2016 TV engines. */
92
+ const maskUrlSecrets = url => url.replace(/([?&][^=&]*(?:token|secret|password|key|authorization)[^=&]*=)[^&]*/gi, '$1«redacted»');
18
93
  /**
19
94
  * Shared low-level beacon helper. Prefers `navigator.sendBeacon` and falls back
20
95
  * to a no-cors `fetch`. Used by the default impression path and available to
@@ -107,21 +182,14 @@ const createStreamLayerProvider = (config = {}) => {
107
182
  return {
108
183
  id: 'streamlayer',
109
184
  resolve: function () {
110
- var _ref = _rollupPluginBabelHelpers.asyncToGenerator(function* (creativeId, signal) {
111
- const url = buildCreativeUrl(creativeApiBase, creativeId);
112
- const response = yield fetch(url, {
113
- signal
114
- });
115
- if (!response.ok) {
116
- throw new Error(`creative request failed: ${response.status}`);
117
- }
118
- const raw = yield response.json();
185
+ var _ref2 = _rollupPluginBabelHelpers.asyncToGenerator(function* (creativeId, signal) {
186
+ const raw = yield fetchCreativeJson('streamlayer', buildCreativeUrl(creativeApiBase, creativeId), signal);
119
187
  return mapApiResponse(_rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, raw), {}, {
120
188
  creativeId: raw.creativeId || creativeId
121
189
  }));
122
190
  });
123
- return function resolve(_x, _x2) {
124
- return _ref.apply(this, arguments);
191
+ return function resolve(_x4, _x5) {
192
+ return _ref2.apply(this, arguments);
125
193
  };
126
194
  }(),
127
195
  fireImpression: fireDefaultImpression
@@ -168,23 +236,17 @@ const createQuortexProvider = (config = {}) => {
168
236
  return {
169
237
  id: 'quortex',
170
238
  resolve: function () {
171
- var _ref2 = _rollupPluginBabelHelpers.asyncToGenerator(function* (adId, signal) {
239
+ var _ref3 = _rollupPluginBabelHelpers.asyncToGenerator(function* (adId, signal) {
172
240
  if (!adApiBase) {
173
241
  throw new Error('quortex provider is missing `adApiBase`');
174
242
  }
175
- const response = yield fetch(buildQuortexUrl(adApiBase, tenantName, adId), {
176
- signal
177
- });
178
- if (!response.ok) {
179
- throw new Error(`quortex creative request failed: ${response.status}`);
180
- }
181
- const raw = yield response.json();
243
+ const raw = yield fetchCreativeJson('quortex', buildQuortexUrl(adApiBase, tenantName, adId), signal);
182
244
  return mapApiResponse(_rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, raw), {}, {
183
245
  creativeId: raw.creativeId || adId
184
246
  }));
185
247
  });
186
- return function resolve(_x3, _x4) {
187
- return _ref2.apply(this, arguments);
248
+ return function resolve(_x6, _x7) {
249
+ return _ref3.apply(this, arguments);
188
250
  };
189
251
  }(),
190
252
  fireImpression: fireDefaultImpression
@@ -192,7 +254,7 @@ const createQuortexProvider = (config = {}) => {
192
254
  };
193
255
  // --- VAST provider ---------------------------------------------------------
194
256
  const mapVastResponse = /*#__PURE__*/function () {
195
- var _ref3 = _rollupPluginBabelHelpers.asyncToGenerator(function* (creativeId, signal) {
257
+ var _ref4 = _rollupPluginBabelHelpers.asyncToGenerator(function* (creativeId, signal) {
196
258
  var _validAd$advertiser$f;
197
259
  const vastClient$1 = new vastClient.VASTClient();
198
260
  const parsedVAST = yield vastClient$1.get(creativeId, {
@@ -243,8 +305,8 @@ const mapVastResponse = /*#__PURE__*/function () {
243
305
  companion
244
306
  };
245
307
  });
246
- return function mapVastResponse(_x5, _x6) {
247
- return _ref3.apply(this, arguments);
308
+ return function mapVastResponse(_x8, _x9) {
309
+ return _ref4.apply(this, arguments);
248
310
  };
249
311
  }();
250
312
  /**
@@ -1,6 +1,7 @@
1
1
  import { Advertisement } from './advertisement.js';
2
2
  export { isFullbleedVideoTemplate } from './units/external-pause-ad.js';
3
3
  export { INSIGHT_BODY_MAX_LENGTH } from './units/pause-ad-card.js';
4
+ export { selectSponsorLogo } from './units/sponsor-logo.js';
4
5
  export { gam } from './units/gam.js';
5
6
  export { createCompanionTracker, createVastTracker, resolveVastTrackerTarget } from './units/vast-tracker.js';
6
7
  import 'core-js/modules/es.array.iterator.js';
@@ -0,0 +1,113 @@
1
+ import 'core-js/modules/es.regexp.exec.js';
2
+ import 'core-js/modules/es.string.replace.js';
3
+
4
+ /**
5
+ * Ad-tag macro substitution, for every ad server — not just GAM.
6
+ *
7
+ * Studio stores a programmatic tag verbatim (`gamRawUrl`, returned untouched by
8
+ * `processGamAdvertisement`), so whatever macros the ad server's own tag builder emitted reach the
9
+ * SDK literal. `gam()` used to fill only the GAM `[placeholder]` token, and only for
10
+ * `pubads.g.doubleclick.net` — every other vendor's tag went out with its macros unexpanded.
11
+ *
12
+ * That is not inert. Measured against the ADITION tag on event 11089
13
+ * (`…/banner?…&ts=[timestamp]&clickurl=[clickmacro]`): ADITION prepends the value of `clickurl` to
14
+ * its own redi URL, so a literal `[clickmacro]` came back inside `<CompanionClickThrough>` as
15
+ * `[clickmacro]https%3A%2F%2Fad1…` — a broken click-through. Sending `clickurl=` empty answers with
16
+ * a valid redi URL.
17
+ *
18
+ * **Two sources.** The cache buster is the SDK's own. The consent family — `${GDPR}` and
19
+ * `${GDPR_CONSENT_<gvlid>}`, IAB TCF, where the id is the vendor's Global Vendor List entry (39 is
20
+ * ADITION / Virtual Minds) — and the click macro come from the host app (`AdTagParams`, set through
21
+ * the `adTagParams` option or `sdk.setAdTagParams`). The SDK never reads a CMP and never guesses
22
+ * consent: a value the host did not pass goes out EMPTY, which ad servers read as "unknown" — never
23
+ * as `gdpr=0`. Empty rather than literal because a literal is not inert either: ADITION copied
24
+ * `${GDPR}` into its own redirect URL (`gdpr%3D%24%7BGDPR%7D`). See
25
+ * `docs/wiki/programmatic-ads-macros.md`.
26
+ *
27
+ * An unknown macro is left exactly as configured, which is what keeps the GAM `[placeholder]`
28
+ * tokens for the PAL path (`gam.ts` fills those per parameter, not per token).
29
+ */
30
+ /**
31
+ * All three spellings in the wild — `[NAME]` (IAB / SpotX), `${NAME}` (Google) and bare `{NAME}` —
32
+ * each with its delimiters either literal or PERCENT-ENCODED.
33
+ *
34
+ * Neither half of that is theoretical. ADITION emitted `[timestamp]` / `[clickmacro]` for the
35
+ * campaign on event 11089 and `{CACHEBUSTER}` / `{GDPR}` for the one on 6203 — same ad server, same
36
+ * advertiser, different tag builder. And Studio stores the 6203 tag as `ts=%7BCACHEBUSTER%7D`, so
37
+ * matching only literal braces made the whole substitution a silent no-op on it: an unexpanded
38
+ * macro raises no error and the ad server still answers 200. `searchParams.get()` decodes on read,
39
+ * which is what hides the encoding from anyone inspecting the parsed URL.
40
+ *
41
+ * The `$` is optional rather than a fourth branch — an unowned `${NAME}` still returns its full
42
+ * matched text, `$` included, so the brace forms cannot corrupt each other.
43
+ */
44
+ const MACRO_PATTERN = /(?:\[|%5B)([a-z0-9_]+)(?:\]|%5D)|(?:\$|%24)?(?:\{|%7B)([a-z0-9_]+)(?:\}|%7D)/gi;
45
+ /**
46
+ * Every host value lands inside a query-string VALUE, so it is encoded exactly once. That matters
47
+ * most for the click URL, which carries its own `?`/`&`: sent raw it re-opens the tag's query, the
48
+ * way the malformed 6203 tag did (`clickurl` truncated, every following parameter duplicated).
49
+ */
50
+ const hostValue = value => value ? encodeURIComponent(value) : '';
51
+ const gdprConsent = ({
52
+ params
53
+ }) => hostValue(params === null || params === void 0 ? void 0 : params.tcString);
54
+ /**
55
+ * Macro name (lower-cased) → the value this SDK sends for it.
56
+ *
57
+ * `clickurl` / `clickmacro` take the host's click tracker, which the ad server PREPENDS to its own
58
+ * redirect — so the viewer's click passes host tracker → vendor redirect → landing page. The SDK
59
+ * does not fire that tracker itself; it would count the click twice. Without one it is empty, the
60
+ * documented "no player tracker" value. VAST click beacons are reported separately by
61
+ * `useSidebarVastTracker`.
62
+ */
63
+ const MACRO_VALUES = {
64
+ // One family, one value: every name here means "make this request unique so nothing caches it".
65
+ // Epoch milliseconds satisfies that for all of them, and keeps the value already shipped for
66
+ // `timestamp` unchanged. (IAB defines `[CACHEBUSTING]` as a random 8-digit number and
67
+ // `[TIMESTAMP]` as ISO-8601, but those describe VAST *tracking* URLs; this substitution only
68
+ // ever touches the ad REQUEST url, where every ad server documents the parameter as a number.)
69
+ timestamp: ({
70
+ now
71
+ }) => String(now),
72
+ cachebuster: ({
73
+ now
74
+ }) => String(now),
75
+ cachebusting: ({
76
+ now
77
+ }) => String(now),
78
+ clickmacro: ({
79
+ params
80
+ }) => hostValue(params === null || params === void 0 ? void 0 : params.clickUrl),
81
+ clickurl: ({
82
+ params
83
+ }) => hostValue(params === null || params === void 0 ? void 0 : params.clickUrl),
84
+ // `1` / `0` only from an explicit boolean — an absent value is unknown, not "does not apply".
85
+ gdpr: ({
86
+ params
87
+ }) => (params === null || params === void 0 ? void 0 : params.gdprApplies) === undefined ? '' : params.gdprApplies ? '1' : '0',
88
+ gdpr_consent: gdprConsent
89
+ };
90
+ /** `GDPR_CONSENT_<gvlid>`: the vendor id names whose tag this is; the value is the same TC string. */
91
+ const GDPR_CONSENT_FOR_VENDOR = /^gdpr_consent_\d+$/;
92
+ const resolveMacro = name => {
93
+ var _MACRO_VALUES$name;
94
+ return (_MACRO_VALUES$name = MACRO_VALUES[name]) !== null && _MACRO_VALUES$name !== void 0 ? _MACRO_VALUES$name : GDPR_CONSENT_FOR_VENDOR.test(name) ? gdprConsent : undefined;
95
+ };
96
+ /**
97
+ * Fill the macros this SDK knows in an ad-tag URL. One timestamp per call, so a tag naming the macro
98
+ * twice stays self-consistent. `params` is read as given — the caller passes the host's CURRENT
99
+ * values at request time.
100
+ */
101
+ const substituteAdTagMacros = (url, params) => {
102
+ const now = Date.now();
103
+ return url.replace(MACRO_PATTERN, (match, bracketed, braced) => {
104
+ var _ref;
105
+ const value = resolveMacro(((_ref = bracketed !== null && bracketed !== void 0 ? bracketed : braced) !== null && _ref !== void 0 ? _ref : '').toLowerCase());
106
+ return value ? value({
107
+ now,
108
+ params
109
+ }) : match;
110
+ });
111
+ };
112
+
113
+ export { substituteAdTagMacros };