@streamlayer/feature-advertisement 1.2.0 → 1.2.1-rc.11.pd437c711

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 (96) hide show
  1. package/classic/package.json +5 -0
  2. package/lib/advertisement.d.ts +6 -6
  3. package/lib/advertisement.js +9 -9
  4. package/lib/cjs/advertisement.d.ts +64 -0
  5. package/lib/cjs/advertisement.js +322 -0
  6. package/lib/cjs/index.d.ts +21 -0
  7. package/lib/cjs/index.js +41 -0
  8. package/lib/cjs/package.json +1 -0
  9. package/lib/cjs/units/ad-macros.d.ts +7 -0
  10. package/lib/cjs/units/ad-macros.js +91 -0
  11. package/lib/cjs/units/external-ad.d.ts +127 -0
  12. package/lib/cjs/units/external-ad.js +421 -0
  13. package/lib/cjs/units/external-pause-ad.d.ts +50 -0
  14. package/lib/cjs/units/external-pause-ad.js +259 -0
  15. package/lib/cjs/units/gam.d.ts +26 -0
  16. package/lib/cjs/units/gam.js +152 -0
  17. package/lib/cjs/units/parse-vast-extensions.d.ts +26 -0
  18. package/lib/cjs/units/parse-vast-extensions.js +56 -0
  19. package/lib/cjs/units/pause-ad-card.d.ts +49 -0
  20. package/lib/cjs/units/pause-ad-card.js +136 -0
  21. package/lib/cjs/units/pause-ad.d.ts +16 -0
  22. package/lib/cjs/units/pause-ad.js +52 -0
  23. package/lib/cjs/units/sponsor-logo.d.ts +78 -0
  24. package/lib/cjs/units/sponsor-logo.js +194 -0
  25. package/lib/cjs/units/ssai-ad-break.d.ts +21 -0
  26. package/lib/cjs/units/ssai-ad-break.js +11 -0
  27. package/lib/cjs/units/ssai-creative.d.ts +89 -0
  28. package/lib/cjs/units/ssai-creative.js +136 -0
  29. package/lib/cjs/units/ssai-detector.d.ts +180 -0
  30. package/lib/cjs/units/ssai-detector.js +1172 -0
  31. package/lib/cjs/units/ssai-log.d.ts +16 -0
  32. package/lib/cjs/units/ssai-log.js +23 -0
  33. package/lib/cjs/units/ssai-preload.d.ts +20 -0
  34. package/lib/cjs/units/ssai-preload.js +62 -0
  35. package/lib/cjs/units/ssai-providers.d.ts +117 -0
  36. package/lib/cjs/units/ssai-providers.js +300 -0
  37. package/lib/cjs/units/vast-tracker.d.ts +45 -0
  38. package/lib/cjs/units/vast-tracker.js +64 -0
  39. package/lib/classic/_virtual/_rollupPluginBabelHelpers.js +71 -0
  40. package/lib/classic/advertisement.js +348 -0
  41. package/lib/classic/cjs/_virtual/_rollupPluginBabelHelpers.js +77 -0
  42. package/lib/classic/cjs/advertisement.js +350 -0
  43. package/lib/classic/cjs/index.js +49 -0
  44. package/lib/classic/cjs/package.json +1 -0
  45. package/lib/classic/cjs/units/ad-macros.js +115 -0
  46. package/lib/classic/cjs/units/external-ad.js +500 -0
  47. package/lib/classic/cjs/units/external-pause-ad.js +278 -0
  48. package/lib/classic/cjs/units/gam.js +173 -0
  49. package/lib/classic/cjs/units/parse-vast-extensions.js +60 -0
  50. package/lib/classic/cjs/units/pause-ad-card.js +134 -0
  51. package/lib/classic/cjs/units/pause-ad.js +72 -0
  52. package/lib/classic/cjs/units/sponsor-logo.js +201 -0
  53. package/lib/classic/cjs/units/ssai-ad-break.js +11 -0
  54. package/lib/classic/cjs/units/ssai-creative.js +142 -0
  55. package/lib/classic/cjs/units/ssai-detector.js +1195 -0
  56. package/lib/classic/cjs/units/ssai-log.js +23 -0
  57. package/lib/classic/cjs/units/ssai-preload.js +72 -0
  58. package/lib/classic/cjs/units/ssai-providers.js +361 -0
  59. package/lib/classic/cjs/units/vast-tracker.js +81 -0
  60. package/lib/classic/index.js +29 -0
  61. package/lib/classic/units/ad-macros.js +113 -0
  62. package/lib/classic/units/external-ad.js +491 -0
  63. package/lib/classic/units/external-pause-ad.js +275 -0
  64. package/lib/classic/units/gam.js +171 -0
  65. package/lib/classic/units/parse-vast-extensions.js +58 -0
  66. package/lib/classic/units/pause-ad-card.js +131 -0
  67. package/lib/classic/units/pause-ad.js +70 -0
  68. package/lib/classic/units/sponsor-logo.js +197 -0
  69. package/lib/classic/units/ssai-ad-break.js +9 -0
  70. package/lib/classic/units/ssai-creative.js +140 -0
  71. package/lib/classic/units/ssai-detector.js +1192 -0
  72. package/lib/classic/units/ssai-log.js +21 -0
  73. package/lib/classic/units/ssai-preload.js +68 -0
  74. package/lib/classic/units/ssai-providers.js +351 -0
  75. package/lib/classic/units/vast-tracker.js +77 -0
  76. package/lib/index.d.ts +15 -14
  77. package/lib/index.js +9 -9
  78. package/lib/units/external-ad.d.ts +2 -2
  79. package/lib/units/external-ad.js +3 -3
  80. package/lib/units/external-pause-ad.js +1 -1
  81. package/lib/units/gam.js +1 -1
  82. package/lib/units/pause-ad.d.ts +1 -1
  83. package/lib/units/pause-ad.js +1 -1
  84. package/lib/units/sponsor-logo.d.ts +12 -4
  85. package/lib/units/sponsor-logo.js +4 -3
  86. package/lib/units/ssai-ad-break.d.ts +1 -1
  87. package/lib/units/ssai-creative.d.ts +1 -1
  88. package/lib/units/ssai-creative.js +1 -1
  89. package/lib/units/ssai-detector.d.ts +1 -1
  90. package/lib/units/ssai-detector.js +1 -1
  91. package/lib/units/ssai-preload.d.ts +1 -1
  92. package/lib/units/ssai-providers.d.ts +1 -1
  93. package/lib/units/ssai-providers.js +1 -1
  94. package/lib/units/vast-tracker.d.ts +1 -1
  95. package/lib/vast-client.d.ts +9 -0
  96. package/package.json +42 -9
@@ -0,0 +1,21 @@
1
+ import { createLogger } from '@streamlayer/sdk-web-logger/classic';
2
+
3
+ /**
4
+ * `ssaiq` — the SSAI ad NARRATIVE log: one line per thing that happens to a
5
+ * server-stitched ad, in the order it happens. The manifest tag the playout
6
+ * published, the metadata round trip it triggers, the break the SDK opens from
7
+ * it, and every beacon fired against it.
8
+ *
9
+ * Named for the Quortex/Synamedia DAI integration it was built for, but it is
10
+ * the path itself that is logged, so a StreamLayer-dialect stream tells the same
11
+ * story. Detector internals — clock sources, poll ticks, fold bookkeeping — stay
12
+ * on `ssai:*`, where `SL_DEBUG` can reach them without filling a demo window.
13
+ *
14
+ * A host publishes it to the page with `SL_LOG_EVENTS=ssaiq` (see
15
+ * `sdk-web-logger`); the demo's log window renders exactly that.
16
+ */
17
+ // Explicit type: the inferred pino type is not nameable across package
18
+ // boundaries (TS2742) when this file is compiled on its own.
19
+ const ssaiq = createLogger('ssaiq');
20
+
21
+ export { ssaiq };
@@ -0,0 +1,68 @@
1
+ import 'core-js/modules/es.array.iterator.js';
2
+ import 'core-js/modules/es.promise.js';
3
+ import 'core-js/modules/esnext.iterator.constructor.js';
4
+ import 'core-js/modules/esnext.iterator.filter.js';
5
+ import 'core-js/modules/web.dom-collections.iterator.js';
6
+ import { createLogger } from '@streamlayer/sdk-web-logger/classic';
7
+
8
+ const log = createLogger('ssai:preload');
9
+ /**
10
+ * How long the overlay is willing to wait for artwork. A slow or dead image host
11
+ * must never hold the bar back — the ad is already playing, and a bar that shows
12
+ * late is worse than one that shows without its logo (the logo is hidden on error
13
+ * anyway). Warming the browser cache is the goal; blocking is not.
14
+ */
15
+ const DEFAULT_PRELOAD_TIMEOUT_MS = 2000;
16
+ /**
17
+ * Fetch images into the browser cache and resolve once they are all settled, or
18
+ * once `timeoutMs` elapses — whichever comes first. Never rejects: a creative
19
+ * whose artwork fails is still a creative worth showing.
20
+ */
21
+ const preloadImages = (urls, timeoutMs = DEFAULT_PRELOAD_TIMEOUT_MS) => {
22
+ const targets = urls.filter(url => !!url);
23
+ // `Image` is absent in SSR/worker contexts; there is nothing to warm there.
24
+ if (targets.length === 0 || typeof Image === 'undefined') {
25
+ return Promise.resolve();
26
+ }
27
+ return new Promise(resolve => {
28
+ let settled = false;
29
+ let remaining = targets.length;
30
+ const finish = () => {
31
+ if (settled) {
32
+ return;
33
+ }
34
+ settled = true;
35
+ clearTimeout(timer);
36
+ resolve();
37
+ };
38
+ const timer = setTimeout(() => {
39
+ log.debug({
40
+ remaining
41
+ }, 'ssai preload timed out, showing anyway');
42
+ finish();
43
+ }, timeoutMs);
44
+ const one = () => {
45
+ remaining -= 1;
46
+ if (remaining <= 0) {
47
+ finish();
48
+ }
49
+ };
50
+ for (const url of targets) {
51
+ const image = new Image();
52
+ image.onload = one;
53
+ image.onerror = one;
54
+ image.src = url;
55
+ }
56
+ });
57
+ };
58
+ /**
59
+ * Warm every image the creative carries — the overlay's brand logo, and the
60
+ * companion banner for hosts that render one — so the bar fades in complete
61
+ * rather than popping its artwork in a beat later.
62
+ */
63
+ const preloadCreativeAssets = (creative, timeoutMs) => {
64
+ var _creative$companion;
65
+ return preloadImages([creative.iconUrl, (_creative$companion = creative.companion) === null || _creative$companion === void 0 ? void 0 : _creative$companion.staticResource], timeoutMs);
66
+ };
67
+
68
+ export { DEFAULT_PRELOAD_TIMEOUT_MS, preloadCreativeAssets, preloadImages };
@@ -0,0 +1,351 @@
1
+ import { asyncToGenerator as _asyncToGenerator, objectSpread2 as _objectSpread2 } from '../_virtual/_rollupPluginBabelHelpers.js';
2
+ import 'core-js/modules/es.error.cause.js';
3
+ import 'core-js/modules/es.array.iterator.js';
4
+ import 'core-js/modules/es.promise.js';
5
+ import 'core-js/modules/es.regexp.exec.js';
6
+ import 'core-js/modules/es.string.replace.js';
7
+ import 'core-js/modules/esnext.iterator.constructor.js';
8
+ import 'core-js/modules/esnext.iterator.filter.js';
9
+ import 'core-js/modules/esnext.iterator.find.js';
10
+ import 'core-js/modules/esnext.iterator.map.js';
11
+ import 'core-js/modules/web.dom-collections.iterator.js';
12
+ import 'core-js/modules/web.dom-exception.stack.js';
13
+ import { createLogger } from '@streamlayer/sdk-web-logger/classic';
14
+ import { VASTClient } from '@dailymotion/vast-client';
15
+ import { ssaiq } from './ssai-log.js';
16
+
17
+ const log = createLogger('ssai:provider');
18
+ const DEFAULT_CREATIVE_API_BASE = 'https://hls.next.streamlayer.io/api/creatives/';
19
+ /**
20
+ * One creative-metadata round trip, on the narrative log: the request the SDK
21
+ * made and the body the ad service answered with, verbatim. This is the half of
22
+ * the story the manifest cannot tell — the ad's identity comes from the playout,
23
+ * its content comes from here.
24
+ */
25
+ const fetchCreativeJson = /*#__PURE__*/function () {
26
+ var _ref = _asyncToGenerator(function* (providerId, url, signal) {
27
+ var _raw, _raw2, _raw3;
28
+ // `adApiBase` is customer infrastructure and may carry a key in its query;
29
+ // the request uses the URL as given, the log gets it with such values masked.
30
+ const shown = maskUrlSecrets(url);
31
+ ssaiq.info({
32
+ provider: providerId,
33
+ url: shown
34
+ }, `[GET] → ${shown}`);
35
+ let response;
36
+ try {
37
+ response = yield fetch(url, {
38
+ signal
39
+ });
40
+ } catch (error) {
41
+ // An abort is the SDK cancelling its own request (break ended, creative
42
+ // changed) — a note, not a failure.
43
+ if ((error === null || error === void 0 ? void 0 : error.name) === 'AbortError') {
44
+ ssaiq.debug({
45
+ provider: providerId,
46
+ url: shown
47
+ }, `[GET] ✗ aborted ${shown}`);
48
+ } else {
49
+ ssaiq.warn({
50
+ provider: providerId,
51
+ url: shown,
52
+ error: String(error)
53
+ }, `[GET] ✗ ${shown}`);
54
+ }
55
+ throw error;
56
+ }
57
+ if (!response.ok) {
58
+ ssaiq.warn({
59
+ provider: providerId,
60
+ url: shown,
61
+ status: response.status
62
+ }, `[GET] ✗ ${response.status} ${shown}`);
63
+ throw new Error(`${providerId} creative request failed: ${response.status}`);
64
+ }
65
+ let raw;
66
+ try {
67
+ raw = yield response.json();
68
+ } catch (error) {
69
+ // A 200 with a body that is not JSON would otherwise leave the log looking
70
+ // like a request still in flight.
71
+ ssaiq.warn({
72
+ provider: providerId,
73
+ url: shown,
74
+ error: String(error)
75
+ }, `[GET] ✗ ${response.status} ${shown} — not JSON`);
76
+ throw error;
77
+ }
78
+ ssaiq.info({
79
+ provider: providerId,
80
+ status: response.status,
81
+ body: raw
82
+ }, `[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'}`);
83
+ return raw;
84
+ });
85
+ return function fetchCreativeJson(_x, _x2, _x3) {
86
+ return _ref.apply(this, arguments);
87
+ };
88
+ }();
89
+ /** `?apiKey=…` → `?apiKey=«redacted»`, for URLs that go on a screen. Regex, not `URL`: this ships to 2016 TV engines. */
90
+ const maskUrlSecrets = url => url.replace(/([?&][^=&]*(?:token|secret|password|key|authorization)[^=&]*=)[^&]*/gi, '$1«redacted»');
91
+ /**
92
+ * Shared low-level beacon helper. Prefers `navigator.sendBeacon` and falls back
93
+ * to a no-cors `fetch`. Used by the default impression path and available to
94
+ * providers that fire URL-based tracking pixels.
95
+ */
96
+ const fireBeacon = url => {
97
+ try {
98
+ if (typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {
99
+ navigator.sendBeacon(url);
100
+ } else {
101
+ void fetch(url, {
102
+ mode: 'no-cors'
103
+ }).catch(error => {
104
+ log.warn(error, 'ssai beacon failed');
105
+ });
106
+ }
107
+ } catch (error) {
108
+ log.warn(error, 'ssai beacon failed');
109
+ }
110
+ };
111
+ const resolveAdvertiser = (advertiser, title) => {
112
+ if (advertiser) {
113
+ return advertiser;
114
+ }
115
+ if (title) {
116
+ return title;
117
+ }
118
+ return 'Ad';
119
+ };
120
+ const buildCreativeUrl = (base, creativeId) => {
121
+ const normalizedBase = base.endsWith('/') ? base : `${base}/`;
122
+ return `${normalizedBase}${encodeURIComponent(creativeId)}`;
123
+ };
124
+ const mapApiResponse = raw => {
125
+ const trackingEvents = Array.isArray(raw.trackingEvents) ? raw.trackingEvents.filter(entry => Boolean((entry === null || entry === void 0 ? void 0 : entry.event) && (entry === null || entry === void 0 ? void 0 : entry.url))).map(entry => ({
126
+ event: entry.event,
127
+ url: entry.url
128
+ })) : [];
129
+ const rawCompanion = Array.isArray(raw.companions) ? raw.companions.find(entry => !!(entry !== null && entry !== void 0 && entry.staticResource)) : undefined;
130
+ const companion = rawCompanion !== null && rawCompanion !== void 0 && rawCompanion.staticResource ? {
131
+ staticResource: rawCompanion.staticResource,
132
+ width: Number(rawCompanion.width) || 0,
133
+ height: Number(rawCompanion.height) || 0,
134
+ clickThrough: rawCompanion.clickThrough,
135
+ clickTracking: Array.isArray(rawCompanion.clickTracking) ? rawCompanion.clickTracking.filter(url => typeof url === 'string' && !!url) : []
136
+ } : undefined;
137
+ return {
138
+ creativeId: raw.creativeId,
139
+ advertiser: resolveAdvertiser(raw.advertiser, raw.title),
140
+ title: raw.title || resolveAdvertiser(raw.advertiser, raw.title),
141
+ iconUrl: raw.iconUrl || undefined,
142
+ clickThroughUrl: raw.clickThroughUrl || undefined,
143
+ trackingEvents,
144
+ companion
145
+ };
146
+ };
147
+ /**
148
+ * Fire every tracking event with the given name, and report how many went out.
149
+ *
150
+ * ALL matches, not the first: the creative contract states that a creative may
151
+ * repeat an `event` value to carry several URLs, and that consumers must not
152
+ * assume uniqueness. Today's payloads carry one URL per event, so this is not a
153
+ * change in what goes on the wire — it just stops being wrong the day a server
154
+ * sends two.
155
+ */
156
+ const fireTrackingEvents = (creative, event) => {
157
+ const matches = creative.trackingEvents.filter(entry => entry.event === event);
158
+ for (const match of matches) {
159
+ fireBeacon(match.url);
160
+ }
161
+ return matches.length;
162
+ };
163
+ /**
164
+ * Shared default impression behavior: fire the `impression` tracking events.
165
+ * Used by the StreamLayer provider and as the fallback in
166
+ * {@link SsaiCreativeCache} when a provider doesn't override `fireImpression`.
167
+ * Exported so both call sites share one definition.
168
+ */
169
+ const fireDefaultImpression = creative => {
170
+ fireTrackingEvents(creative, 'impression');
171
+ };
172
+ /**
173
+ * StreamLayer's internal creative provider. Resolves creatives from the JSON
174
+ * creative API (`GET {creativeApiBase}{encodeURIComponent(creativeId)}`) and
175
+ * fires impressions from the `'impression'` tracking event via `sendBeacon`.
176
+ */
177
+ const createStreamLayerProvider = (config = {}) => {
178
+ var _config$creativeApiBa;
179
+ const creativeApiBase = (_config$creativeApiBa = config.creativeApiBase) !== null && _config$creativeApiBa !== void 0 ? _config$creativeApiBa : DEFAULT_CREATIVE_API_BASE;
180
+ return {
181
+ id: 'streamlayer',
182
+ resolve: function () {
183
+ var _ref2 = _asyncToGenerator(function* (creativeId, signal) {
184
+ const raw = yield fetchCreativeJson('streamlayer', buildCreativeUrl(creativeApiBase, creativeId), signal);
185
+ return mapApiResponse(_objectSpread2(_objectSpread2({}, raw), {}, {
186
+ creativeId: raw.creativeId || creativeId
187
+ }));
188
+ });
189
+ return function resolve(_x4, _x5) {
190
+ return _ref2.apply(this, arguments);
191
+ };
192
+ }(),
193
+ fireImpression: fireDefaultImpression
194
+ };
195
+ };
196
+ const buildQuortexUrl = (base, tenantName, adId) => {
197
+ // Deliberately string concatenation rather than `URL`/`URLSearchParams`: this
198
+ // package ships to 2016-era TV engines (see the chrome-32 classic build).
199
+ const separator = base.indexOf('?') === -1 ? '?' : '&';
200
+ const tenant = tenantName ? `TenantName=${encodeURIComponent(tenantName)}&` : '';
201
+ return `${base}${separator}${tenant}adID=${encodeURIComponent(adId)}&info=1`;
202
+ };
203
+ /**
204
+ * Quortex / Synamedia DAI ad metadata provider.
205
+ *
206
+ * `GET {adApiBase}?TenantName={tenantName}&adID={adId}&info=1` (the `TenantName`
207
+ * part omitted when `adApiBase` already carries it) — the ad id comes
208
+ * from the manifest's `X-AD-ID` DATERANGE attribute (see `ssai-detector.ts`, which
209
+ * reconstructs the ad window from the tracking-marker burst those tags form).
210
+ *
211
+ * The response is byte-compatible with the StreamLayer creative API, so the same
212
+ * `mapApiResponse` normalizes both.
213
+ *
214
+ * Known limitation: impressions fire the `impression` tracking event from the API
215
+ * response, not the per-session beacon the manifest carries in `X-AD-URL`. The two
216
+ * differ in `trackingid`/`USERID`, so Quortex-side attribution will see the
217
+ * generic id rather than the viewer's session until the manifest beacons are
218
+ * plumbed through to the provider.
219
+ */
220
+ const createQuortexProvider = (config = {}) => {
221
+ const {
222
+ adApiBase,
223
+ tenantName
224
+ } = config;
225
+ if (!adApiBase) {
226
+ // Loud, but not thrown: this runs inside the host's render effect via
227
+ // `startSsaiMonitoring`, and taking the app down over a config typo is worse
228
+ // than an overlay that never appears and says why.
229
+ log.error({
230
+ adApiBase,
231
+ tenantName
232
+ }, 'quortex provider requires `adApiBase`');
233
+ }
234
+ return {
235
+ id: 'quortex',
236
+ resolve: function () {
237
+ var _ref3 = _asyncToGenerator(function* (adId, signal) {
238
+ if (!adApiBase) {
239
+ throw new Error('quortex provider is missing `adApiBase`');
240
+ }
241
+ const raw = yield fetchCreativeJson('quortex', buildQuortexUrl(adApiBase, tenantName, adId), signal);
242
+ return mapApiResponse(_objectSpread2(_objectSpread2({}, raw), {}, {
243
+ creativeId: raw.creativeId || adId
244
+ }));
245
+ });
246
+ return function resolve(_x6, _x7) {
247
+ return _ref3.apply(this, arguments);
248
+ };
249
+ }(),
250
+ fireImpression: fireDefaultImpression
251
+ };
252
+ };
253
+ // --- VAST provider ---------------------------------------------------------
254
+ const mapVastResponse = /*#__PURE__*/function () {
255
+ var _ref4 = _asyncToGenerator(function* (creativeId, signal) {
256
+ var _validAd$advertiser$f;
257
+ const vastClient = new VASTClient();
258
+ const parsedVAST = yield vastClient.get(creativeId, {
259
+ timeout: 8000
260
+ });
261
+ if (signal.aborted) {
262
+ throw new DOMException('aborted', 'AbortError');
263
+ }
264
+ const validAd = parsedVAST.ads.find(ad => ad.creatives.length > 0);
265
+ if (!validAd) {
266
+ throw new Error('no_valid_ad');
267
+ }
268
+ const advertiserValue = Array.isArray(validAd.advertiser) ? (_validAd$advertiser$f = validAd.advertiser.find(entry => !!(entry !== null && entry !== void 0 && entry.value))) === null || _validAd$advertiser$f === void 0 ? void 0 : _validAd$advertiser$f.value : undefined;
269
+ const advertiser = resolveAdvertiser(advertiserValue, validAd.title || undefined);
270
+ let iconUrl;
271
+ let clickThroughUrl;
272
+ let companion;
273
+ for (const creative of validAd.creatives) {
274
+ if (creative.type === 'linear') {
275
+ var _linear$icons, _linear$videoClickThr;
276
+ const linear = creative;
277
+ iconUrl = iconUrl || ((_linear$icons = linear.icons) === null || _linear$icons === void 0 || (_linear$icons = _linear$icons.find(icon => !!(icon !== null && icon !== void 0 && icon.staticResource))) === null || _linear$icons === void 0 ? void 0 : _linear$icons.staticResource) || undefined;
278
+ clickThroughUrl = clickThroughUrl || ((_linear$videoClickThr = linear.videoClickThroughURLTemplate) === null || _linear$videoClickThr === void 0 ? void 0 : _linear$videoClickThr.url);
279
+ }
280
+ if (creative.type === 'companion') {
281
+ var _creative$variations, _variation$staticReso;
282
+ const variation = (_creative$variations = creative.variations) === null || _creative$variations === void 0 ? void 0 : _creative$variations[0];
283
+ const staticResource = 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;
284
+ if (staticResource && !companion) {
285
+ var _variation$companionC, _variation$companionC2;
286
+ companion = {
287
+ staticResource,
288
+ width: Number(variation === null || variation === void 0 ? void 0 : variation.width) || 0,
289
+ height: Number(variation === null || variation === void 0 ? void 0 : variation.height) || 0,
290
+ clickThrough: variation === null || variation === void 0 || (_variation$companionC = variation.companionClickThroughURLTemplate) === null || _variation$companionC === void 0 ? void 0 : _variation$companionC.url,
291
+ clickTracking: ((_variation$companionC2 = variation === null || variation === void 0 ? void 0 : variation.companionClickTrackingURLTemplates) !== null && _variation$companionC2 !== void 0 ? _variation$companionC2 : []).map(entry => entry === null || entry === void 0 ? void 0 : entry.url).filter(url => !!url)
292
+ };
293
+ }
294
+ }
295
+ }
296
+ return {
297
+ creativeId,
298
+ advertiser,
299
+ title: validAd.title || advertiser,
300
+ iconUrl,
301
+ clickThroughUrl,
302
+ trackingEvents: [],
303
+ companion
304
+ };
305
+ });
306
+ return function mapVastResponse(_x8, _x9) {
307
+ return _ref4.apply(this, arguments);
308
+ };
309
+ }();
310
+ /**
311
+ * Maps a VAST tag into the normalized SsaiCreative shape.
312
+ *
313
+ * Note: `fireImpression` is intentionally left to the shared default for now.
314
+ * Proper VAST impression/tracking wiring through `VASTTracker` is a documented
315
+ * follow-up; it requires retaining the parsed ad/tracker per creative.
316
+ */
317
+ const createVastProvider = (_config = {}) => {
318
+ return {
319
+ id: 'vast',
320
+ resolve: mapVastResponse
321
+ };
322
+ };
323
+ /** Built-in provider factories keyed by name. */
324
+ const BUILTIN_PROVIDERS = {
325
+ streamlayer: config => createStreamLayerProvider(config),
326
+ quortex: config => createQuortexProvider(config),
327
+ vast: () => createVastProvider()
328
+ };
329
+ const isProviderInstance = option => {
330
+ return typeof option === 'object' && typeof option.resolve === 'function';
331
+ };
332
+ /**
333
+ * Resolve a {@link SsaiProviderOption} into a concrete {@link SsaiCreativeProvider}.
334
+ * Defaults to the StreamLayer provider (using `fallbackConfig`) when no option is
335
+ * supplied.
336
+ */
337
+ const resolveProvider = (option, fallbackConfig) => {
338
+ var _option$config;
339
+ if (!option) {
340
+ return createStreamLayerProvider(fallbackConfig);
341
+ }
342
+ if (typeof option === 'string') {
343
+ return BUILTIN_PROVIDERS[option](fallbackConfig);
344
+ }
345
+ if (isProviderInstance(option)) {
346
+ return option;
347
+ }
348
+ return BUILTIN_PROVIDERS[option.name]((_option$config = option.config) !== null && _option$config !== void 0 ? _option$config : fallbackConfig);
349
+ };
350
+
351
+ export { BUILTIN_PROVIDERS, DEFAULT_CREATIVE_API_BASE, createQuortexProvider, createStreamLayerProvider, createVastProvider, fireBeacon, fireDefaultImpression, fireTrackingEvents, resolveProvider };
@@ -0,0 +1,77 @@
1
+ import { VASTTracker } from '@dailymotion/vast-client';
2
+
3
+ /**
4
+ * One place that constructs a `VASTTracker`.
5
+ *
6
+ * Every StreamLayer surface that reports VAST beacons — the GAM video player, both pause-ad
7
+ * renderers and the Standard Ad sidebar — needs the same `(ad, creative, variation)` triple, and
8
+ * each was assembling it by hand behind its own `@ts-ignore`. The parsed shapes those triples
9
+ * come from (`AdData` / `AdCompanion` / `AdNonLinear`) are defined in this package, so the
10
+ * mapping belongs here too.
11
+ *
12
+ * Deliberately *only* construction: when a beacon may fire, and how a surface de-duplicates its
13
+ * impression, stays with that surface. Those policies differ on purpose (per-ad-id in module
14
+ * scope for the sidebar, per component instance for the pause ads, per tracker identity for the
15
+ * player) and they are billing-visible, so collapsing them here would silently change counts.
16
+ */
17
+ // eslint-disable-next-line @typescript-eslint/triple-slash-reference
18
+ /// <reference path="../module.d.ts" />
19
+ const createVastTracker = ({
20
+ ad,
21
+ creative,
22
+ variation
23
+ }) =>
24
+ // The first argument is the `VASTClient` the tracker may update; every surface here drives
25
+ // playback itself and only wants the beacons, so it is always `null`.
26
+ //
27
+ // The type shim declares the 3rd argument as `VastCreativeLinear` and the 4th as another
28
+ // *creative*. At runtime `vast-client` accepts any creative in the 3rd and expects the
29
+ // `<Companion>` / `<NonLinear>` *variation* in the 4th. The casts encode that runtime contract
30
+ // once here rather than at every call site.
31
+ new VASTTracker(null, ad, creative, variation);
32
+ /**
33
+ * A tracker for a single `<Companion>`. A `<CompanionAds>` creative has no creative-level
34
+ * `<TrackingEvents>` — each variation owns its own `creativeView` — so a companion is reported
35
+ * through its own tracker. `undefined` when the companion was parsed without them (nothing to
36
+ * report against).
37
+ */
38
+ const createCompanionTracker = (ad, companion) => companion.creative && companion.variation ? createVastTracker({
39
+ ad,
40
+ creative: companion.creative,
41
+ variation: companion.variation
42
+ }) : undefined;
43
+ /**
44
+ * The creative a parsed ad reports its impression / playback beacons through, in the order the
45
+ * shells draw them: the `<Linear>` creative first, then the `<NonLinear>` still, then — for a tag
46
+ * whose only creative is `<CompanionAds>` — the companion that was actually drawn.
47
+ *
48
+ * `drawnCompanion` is a parameter rather than `adData.companion` because only the rendering shell
49
+ * knows which companion reached the screen, and reporting one that did not is a false view.
50
+ */
51
+ const resolveVastTrackerTarget = (adData, drawnCompanion) => {
52
+ // `AdData.creative` is not optional in the type but `getCreative` can return nothing: a
53
+ // programmatic tag does not have to carry a `<Linear>` creative.
54
+ if (adData.creative) {
55
+ return {
56
+ ad: adData.ad,
57
+ creative: adData.creative
58
+ };
59
+ }
60
+ if (adData.nonLinear) {
61
+ return {
62
+ ad: adData.ad,
63
+ creative: adData.nonLinear.creative,
64
+ variation: adData.nonLinear.variation
65
+ };
66
+ }
67
+ if (drawnCompanion !== null && drawnCompanion !== void 0 && drawnCompanion.creative && drawnCompanion.variation) {
68
+ return {
69
+ ad: adData.ad,
70
+ creative: drawnCompanion.creative,
71
+ variation: drawnCompanion.variation
72
+ };
73
+ }
74
+ return undefined;
75
+ };
76
+
77
+ export { createCompanionTracker, createVastTracker, resolveVastTrackerTarget };
package/lib/index.d.ts CHANGED
@@ -1,20 +1,21 @@
1
+ /// <reference path="./vast-client.d.ts" />
1
2
  import { StreamLayerContext } from '@streamlayer/sdk-web-interfaces';
2
3
  declare module '@streamlayer/sdk-web-interfaces' {
3
4
  interface StreamLayerSDK {
4
- advertisement: import('./advertisement').Advertisement;
5
+ advertisement: import('./advertisement.js').Advertisement;
5
6
  }
6
7
  }
7
- export { type ExternalAdStore, type ExternalPauseAdStore, type SsaiMonitorOptions } from './advertisement';
8
- export { type GAMStaticCore, type PauseAdTemplate, isFullbleedVideoTemplate } from './units/external-pause-ad';
9
- export { INSIGHT_BODY_MAX_LENGTH, type PauseAdCard, type PauseAdKind, type PauseAdMediaMode, } from './units/pause-ad-card';
10
- export { type PauseAdStore } from './units/pause-ad';
11
- export { type AdStore, type AdData, type AdCompanion, type AdNonLinear } from './units/external-ad';
12
- export { selectSponsorLogo, type SponsorLogoAsset } from './units/sponsor-logo';
13
- export { gam, type GamNonceResult, type PalNonceManager } from './units/gam';
14
- export { createCompanionTracker, createVastTracker, resolveVastTrackerTarget, type VastTrackerTarget, } from './units/vast-tracker';
15
- export { type SsaiCreative, type SsaiTrackingEvent, type SsaiCompanion, type SsaiProgressEvent, DEFAULT_CREATIVE_API_BASE, } from './units/ssai-creative';
16
- export { type SsaiCreativeProvider, type SsaiProviderOption, type SsaiProviderName, type SsaiProviderConfig, type StreamLayerProviderConfig, type QuortexProviderConfig, createStreamLayerProvider, createQuortexProvider, createVastProvider, resolveProvider, fireBeacon, fireTrackingEvents, BUILTIN_PROVIDERS, } from './units/ssai-providers';
17
- export { type SsaiDateRange, type SsaiTrackingMarker, type HlsLike, type SsaiClockSource, type SsaiDebugEvent, type SsaiDebugRange, type SsaiDebugSnapshot, } from './units/ssai-detector';
18
- export { preloadImages, preloadCreativeAssets, DEFAULT_PRELOAD_TIMEOUT_MS } from './units/ssai-preload';
19
- export { type SsaiAdBreakState, type SsaiAdBreakStatus, type SsaiAdBreakStore } from './units/ssai-ad-break';
8
+ export { type ExternalAdStore, type ExternalPauseAdStore, type SsaiMonitorOptions } from './advertisement.js';
9
+ export { type GAMStaticCore, type PauseAdTemplate, isFullbleedVideoTemplate } from './units/external-pause-ad.js';
10
+ export { INSIGHT_BODY_MAX_LENGTH, type PauseAdCard, type PauseAdKind, type PauseAdMediaMode, } from './units/pause-ad-card.js';
11
+ export { type PauseAdStore } from './units/pause-ad.js';
12
+ export { type AdStore, type AdData, type AdCompanion, type AdNonLinear } from './units/external-ad.js';
13
+ export { selectSponsorLogo, type SponsorLogoAsset } from './units/sponsor-logo.js';
14
+ export { gam, type GamNonceResult, type PalNonceManager } from './units/gam.js';
15
+ export { createCompanionTracker, createVastTracker, resolveVastTrackerTarget, type VastTrackerTarget, } from './units/vast-tracker.js';
16
+ export { type SsaiCreative, type SsaiTrackingEvent, type SsaiCompanion, type SsaiProgressEvent, DEFAULT_CREATIVE_API_BASE, } from './units/ssai-creative.js';
17
+ export { type SsaiCreativeProvider, type SsaiProviderOption, type SsaiProviderName, type SsaiProviderConfig, type StreamLayerProviderConfig, type QuortexProviderConfig, createStreamLayerProvider, createQuortexProvider, createVastProvider, resolveProvider, fireBeacon, fireTrackingEvents, BUILTIN_PROVIDERS, } from './units/ssai-providers.js';
18
+ export { type SsaiDateRange, type SsaiTrackingMarker, type HlsLike, type SsaiClockSource, type SsaiDebugEvent, type SsaiDebugRange, type SsaiDebugSnapshot, } from './units/ssai-detector.js';
19
+ export { preloadImages, preloadCreativeAssets, DEFAULT_PRELOAD_TIMEOUT_MS } from './units/ssai-preload.js';
20
+ export { type SsaiAdBreakState, type SsaiAdBreakStatus, type SsaiAdBreakStore } from './units/ssai-ad-break.js';
20
21
  export declare const advertisement: (instance: StreamLayerContext, _opts: unknown, done: () => void) => void;
package/lib/index.js CHANGED
@@ -1,17 +1,17 @@
1
- import { Advertisement } from './advertisement';
2
- export { isFullbleedVideoTemplate } from './units/external-pause-ad';
1
+ import { Advertisement } from './advertisement.js';
2
+ export { isFullbleedVideoTemplate } from './units/external-pause-ad.js';
3
3
  // Card shapes come straight from the pure module rather than through `units/pause-ad`. NOT a bundle
4
4
  // guarantee for barrel consumers — this file value-imports `Advertisement` on line 3, which imports
5
5
  // the store module anyway, and the package declares no `sideEffects: false`. What it does buy: the
6
6
  // unit spec and any deep importer get the card shapes without the store, and dropping the
7
7
  // `export *` keeps `units/pause-ad` from silently re-exporting `parsePauseAdCards`.
8
- export { INSIGHT_BODY_MAX_LENGTH, } from './units/pause-ad-card';
9
- export { selectSponsorLogo } from './units/sponsor-logo';
10
- export { gam } from './units/gam';
11
- export { createCompanionTracker, createVastTracker, resolveVastTrackerTarget, } from './units/vast-tracker';
12
- export { DEFAULT_CREATIVE_API_BASE, } from './units/ssai-creative';
13
- export { createStreamLayerProvider, createQuortexProvider, createVastProvider, resolveProvider, fireBeacon, fireTrackingEvents, BUILTIN_PROVIDERS, } from './units/ssai-providers';
14
- export { preloadImages, preloadCreativeAssets, DEFAULT_PRELOAD_TIMEOUT_MS } from './units/ssai-preload';
8
+ export { INSIGHT_BODY_MAX_LENGTH, } from './units/pause-ad-card.js';
9
+ export { selectSponsorLogo } from './units/sponsor-logo.js';
10
+ export { gam } from './units/gam.js';
11
+ export { createCompanionTracker, createVastTracker, resolveVastTrackerTarget, } from './units/vast-tracker.js';
12
+ export { DEFAULT_CREATIVE_API_BASE, } from './units/ssai-creative.js';
13
+ export { createStreamLayerProvider, createQuortexProvider, createVastProvider, resolveProvider, fireBeacon, fireTrackingEvents, BUILTIN_PROVIDERS, } from './units/ssai-providers.js';
14
+ export { preloadImages, preloadCreativeAssets, DEFAULT_PRELOAD_TIMEOUT_MS } from './units/ssai-preload.js';
15
15
  export const advertisement = (instance, _opts, done) => {
16
16
  instance.sdk.advertisement = new Advertisement(instance);
17
17
  instance.sdk.onMount({ name: 'advertisement' }, () => {
@@ -2,8 +2,8 @@ import type { AdTagParams } from '@streamlayer/sdk-web-core';
2
2
  import { StreamLayerContext } from '@streamlayer/sdk-web-interfaces';
3
3
  import { VastAd, VastCompanionAd, VastCreativeCompanion, VastCreativeLinear, VastCreativeNonLinear, VastNonLinearAd, VastResponse } from '@dailymotion/vast-client';
4
4
  import '@streamlayer/sdk-web-api';
5
- import { type PalNonceManager } from './gam';
6
- import { VastCreative } from './parse-vast-extensions';
5
+ import { type PalNonceManager } from './gam.js';
6
+ import { VastCreative } from './parse-vast-extensions.js';
7
7
  export type AdCompanion = {
8
8
  imageUrl?: string;
9
9
  clickUrl?: string;
@@ -7,9 +7,9 @@ import { createLogger } from '@streamlayer/sdk-web-logger';
7
7
  import { VASTClient, VASTParser, } from '@dailymotion/vast-client';
8
8
  import '@streamlayer/sdk-web-api';
9
9
  import VMAP from 'vmap';
10
- import { gam } from './gam';
11
- import { parseVastExtension } from './parse-vast-extensions';
12
- import { collectAssets, selectCardLogo } from './sponsor-logo';
10
+ import { gam } from './gam.js';
11
+ import { parseVastExtension } from './parse-vast-extensions.js';
12
+ import { collectAssets, selectCardLogo } from './sponsor-logo.js';
13
13
  const log = createLogger('ui:gam-external-ad');
14
14
  /** Default budget for a single VAST resolution before it is treated as "no ad". */
15
15
  const VAST_FETCH_TIMEOUT_MS = 10_000;
@@ -7,7 +7,7 @@ import { eventBus } from '@streamlayer/sdk-web-interfaces';
7
7
  import { createLogger } from '@streamlayer/sdk-web-logger';
8
8
  import '@streamlayer/sdk-web-api';
9
9
  import { VASTClient } from '@dailymotion/vast-client';
10
- import { substituteAdTagMacros } from './ad-macros';
10
+ import { substituteAdTagMacros } from './ad-macros.js';
11
11
  const FULLBLEED_VIDEO_TEMPLATES = [
12
12
  'fullbleed-video',
13
13
  'fullbleed-video-center',
package/lib/units/gam.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { safeLocalStorage, safeSessionStorage } from '@streamlayer/sdk-web-storage';
2
2
  import { validate as uuidValidate, v5 as uuid5, v4 as uuid4 } from 'uuid';
3
- import { substituteAdTagMacros } from './ad-macros';
3
+ import { substituteAdTagMacros } from './ad-macros.js';
4
4
  const urlIsNotGam = (url) => {
5
5
  return url.origin !== 'https://pubads.g.doubleclick.net';
6
6
  };
@@ -1,7 +1,7 @@
1
1
  import { createSingleStore, StreamLayerContext } from '@streamlayer/sdk-web-interfaces';
2
2
  import '@streamlayer/sdk-web-api';
3
3
  import '@streamlayer/sdk-web-core/store';
4
- import { type PauseAdCard } from './pause-ad-card';
4
+ import { type PauseAdCard } from './pause-ad-card.js';
5
5
  export type PauseAdListOptions = {
6
6
  refetchInterval: number;
7
7
  $enabled: boolean;
@@ -3,7 +3,7 @@ import { atom } from 'nanostores';
3
3
  import '@streamlayer/sdk-web-api';
4
4
  import '@streamlayer/sdk-web-core/store';
5
5
  import { Feed } from '@streamlayer/sl-eslib/interactive/feed/interactive.feed_pb';
6
- import { parsePauseAdCards } from './pause-ad-card';
6
+ import { parsePauseAdCards } from './pause-ad-card.js';
7
7
  const log = createLogger('advertisement:pause-ad');
8
8
  export const pauseAdListStore = (instance) => {
9
9
  const $slStreamId = instance.stores.slStreamId.getAtomStore();