universal-ad-loader 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,27 @@
1
+ # 0.2.0
2
+
3
+ **What's new**
4
+ - `native` creatives: the ad server sends assets (title, body, image, icon, sponsored-by, call to action, rating, price) and UAL draws
5
+ them in the page inside a Shadow DOM, inheriting the site's font and colour. A "Sponsored" label is always shown. Assets are text,
6
+ never HTML; images and links must be http(s). Layouts: `card`, `list`, `compact`; style with the `--ual-native-*` CSS variables.
7
+ - `video` creatives (out-stream): a muted `<video>` that starts when at least half of it is visible and pauses when it leaves the
8
+ screen, with a sound button. No audio autoplay. New trackers: `trackers.start` and `trackers.quartile.{firstQuartile,midpoint,thirdQuartile,complete}`,
9
+ each fired once, and a `videoEvent` event. Video viewability waits 2 seconds instead of 1 (unless the slot sets `viewability.durationMs`).
10
+ - `UALExt.track(name)`: HTML creatives can fire the named trackers declared in `trackers.custom`. Undeclared names are ignored.
11
+ - `slots.X.sticky`: a small banner anchored to the top or bottom edge with a close button. Only when the ad takes at most 30% of
12
+ the screen height (Better Ads); otherwise it stays in the page flow.
13
+ - `width`/`height` are optional for `native` ads.
14
+
15
+ **Good to know**
16
+ - The old tags and configurations keep working unchanged.
17
+ - Video quartiles use the player's `timeupdate`; the file must have a duration (a live recording without one gives no quartiles).
18
+ - The 2-second video viewability rule is the one commonly used for video; check it against the MRC guideline before relying on it for billing.
19
+
20
+ # 0.1.2
21
+
22
+ **Changes**
23
+ - README: example of inline configuration (`ual.cmd.push` + `init({...})`) as an alternative to `data-config`.
24
+
1
25
  # 0.1.1
2
26
 
3
27
  **Changes**
package/README.md CHANGED
@@ -1,11 +1,11 @@
1
- # UAL — Universal Ad Loader
1
+ # UAL — Universal Ad Loader [![NPM version](https://img.shields.io/npm/v/universal-ad-loader)](https://www.npmjs.com/package/universal-ad-loader) [![Downloads](https://img.shields.io/npm/dm/universal-ad-loader)](https://www.npmjs.com/package/universal-ad-loader) [![Node version](https://img.shields.io/node/v/universal-ad-loader)](https://www.npmjs.com/package/universal-ad-loader) [![Bundle size](https://img.shields.io/bundlephobia/minzip/universal-ad-loader)](https://bundlephobia.com/package/universal-ad-loader) [![License](https://img.shields.io/npm/l/universal-ad-loader)](LICENSE)
2
2
 
3
3
  One tag per site. It handles slots, centralized configuration, TCF consent, lazy loading, waterfalls across providers (adapters), sandboxed rendering, viewability and events. ~10 KB gzip.
4
4
 
5
5
  ## Installing on a site
6
6
 
7
7
  ```html
8
- <script async src="https://adcdn.yourdomain.net/ual.min.js"
8
+ <script async src="https://cdn.jsdelivr.net/npm/universal-ad-loader@0.1.2/dist/ual.min.js"
9
9
  data-config="https://adcdn.yourdomain.net/config/site_123.json"></script>
10
10
 
11
11
  <div data-ad-slot="top"></div>
@@ -16,7 +16,30 @@ One tag per site. It handles slots, centralized configuration, TCF consent, lazy
16
16
  - `data-ad-params` (optional) overrides the provider's `params` for that instance.
17
17
  - Divs added later (SPA, infinite scroll) are detected automatically. On a route change, call `ual.pageView()`.
18
18
 
19
- > **What to upload to the CDN:** the loader (`dist/ual.min.js`) and one configuration JSON per site, with the right headers: see `docs/cdn.md`.
19
+ **Inline configuration (no JSON file).** Instead of `data-config`, the configuration can be passed as an object in the page. The `cmd.push` block can go before or after the loader tag:
20
+
21
+ ```html
22
+ <script>
23
+ window.ual = window.ual || { cmd: [] };
24
+ ual.cmd.push(function (u) {
25
+ u.init({
26
+ site: 'site_123',
27
+ providers: { main: { adapter: 'json', endpoint: 'https://ads.yourdomain.net/v1/decide' } },
28
+ slots: { top: { sizes: [[728, 90]], providers: ['main'] } }
29
+ });
30
+ });
31
+ </script>
32
+ <script async src="https://cdn.jsdelivr.net/npm/universal-ad-loader@0.1.2/dist/ual.min.js"></script>
33
+
34
+ <div data-ad-slot="top"></div>
35
+ ```
36
+
37
+ - Use either `data-config` or `init(...)`, not both: a second `init` is ignored with a warning.
38
+ - The configuration lives in the page HTML, so changing it means redeploying the page (with a JSON on the CDN you just replace the file). `u.init('https://.../site_123.json')` also works if you want to choose the URL at runtime.
39
+ - An inline script needs `'unsafe-inline'` or a nonce in the page's CSP; with a strict CSP prefer the external JSON.
40
+ - **Components (React, Next.js, Vue, Astro):** the same applies, and inline is already the default. The `config` prop takes an object (inline) or the URL of a JSON; Astro's `UalBundled` writes the object into an `application/json` block, which works even with a strict CSP. See `docs/libreria.md`.
41
+
42
+ > **Where the files live:** the loader is served for free by [jsDelivr](https://www.jsdelivr.com/) straight from the npm package (nothing to upload; pin the version in the URL, as above). Only the configuration JSON, one per site, goes on your own CDN with the right headers: see `docs/cdn.md`.
20
43
 
21
44
  ## Configuration (JSON served from the CDN)
22
45
 
@@ -101,6 +124,36 @@ Messages are accepted only from the slot's own iframe (checked on `event.source`
101
124
  - `src` (image), `url` (url), `clickUrl` and all `trackers`: only `http:`/`https:` (and `data:image/` for `src`). No `javascript:`.
102
125
  - Sandbox: never `allow-same-origin` for `image`/`html`; never `allow-top-navigation` (a creative cannot redirect the page; `target=_blank` links open a popup). For `type:'url'`, `allow-same-origin` is kept (legacy servers need it) **only if the URL has a different origin from the page**; if it is the same origin it is removed. A creative's `sandbox` override is filtered with the same rules.
103
126
 
127
+ ## Native, video and sticky
128
+
129
+ Besides `image`, `html` and `url`, an ad can be `native` or `video`.
130
+
131
+ ```json
132
+ { "slotId": "feed#1", "type": "native", "clickUrl": "https://ads/c?t=SIGNED",
133
+ "native": { "title": "A headline", "body": "Two lines of text", "sponsoredBy": "ACME",
134
+ "cta": "Learn more", "image": { "src": "https://cdn/.../main.jpg", "width": 1200, "height": 628 },
135
+ "icon": "https://cdn/.../icon.png", "layout": "card" },
136
+ "trackers": { "impression": ["https://ads/i?t=..."], "viewable": ["https://ads/v?t=..."] } }
137
+ ```
138
+
139
+ The assets are plain data (never HTML). UAL draws them in the page inside a Shadow DOM, so the card inherits the site's font and colour;
140
+ a "Sponsored" label is always shown. Style it with CSS variables on the slot: `--ual-native-bg`, `--ual-native-border`,
141
+ `--ual-native-radius`, `--ual-native-accent`, `--ual-native-accent-text`. Title is limited to 150 characters, body to 300 (longer text is cut).
142
+
143
+ ```json
144
+ { "slotId": "article#1", "type": "video", "src": "https://cdn/.../ad.mp4", "mime": "video/mp4", "poster": "https://cdn/.../p.jpg",
145
+ "width": 640, "height": 360, "clickUrl": "https://ads/c?t=SIGNED",
146
+ "trackers": { "impression": ["..."], "viewable": ["..."], "start": ["..."],
147
+ "quartile": { "firstQuartile": ["..."], "midpoint": ["..."], "thirdQuartile": ["..."], "complete": ["..."] } } }
148
+ ```
149
+
150
+ The video starts **muted** when at least half of it is visible and pauses when it is not; the visitor can turn the sound on. Each tracker fires once.
151
+ The `videoEvent` event reports `start`, `firstQuartile`, `midpoint`, `thirdQuartile` and `complete`. Video is counted as viewable after 2 s in view (set `viewability.durationMs` on the slot to change it).
152
+
153
+ `UALExt.track(name)` lets an HTML creative fire a named tracker declared in `trackers.custom` (`{ "engaged": ["https://ads/e?t=..."] }`); names that were not declared are ignored.
154
+
155
+ `slots.X.sticky` (`true`, `"top"`, `"bottom"` or `{ "position": "bottom" }`) anchors a small banner to the screen edge with a close button. It only applies if the ad is at most 30% of the screen height; larger ads stay in the page.
156
+
104
157
  ## JavaScript API
105
158
 
106
159
  ```js
@@ -187,7 +240,7 @@ To work on one part at a time (this is what CI does, in parallel jobs):
187
240
  |---|---|---|
188
241
  | `npm run lint` | oxlint on the sources | base dependencies only |
189
242
  | `npm run test:node` | `test/package.mjs` | base dependencies only (no browser) |
190
- | `npm run test:browser` | `test/run.mjs` and `test/regression.mjs` | + Chromium |
243
+ | `npm run test:browser` | `test/run.mjs`, `test/regression.mjs` and `test/formats.mjs` | + Chromium |
191
244
  | `npm run test:frameworks` | `test/wrappers.mjs` | + `test/heavy` and Chromium |
192
245
 
193
246
  Every suite also writes a JUnit report to `test-results/` (CI shows it in the Tests tab).
@@ -196,6 +249,7 @@ Every suite also writes a JUnit report to `test-results/` (CI shows it in the Te
196
249
  |---|---|
197
250
  | `test/run.mjs` | loader e2e with a fake ad server and a fake CMP: batching per provider, waterfall to house, lazy load, `sizeMapping`, consent, `pageRules`, trackers, expand, sandbox |
198
251
  | `test/regression.mjs` | render security, `setTargeting`, late CMP, `contextual` providers, slots moved/closed in the DOM, refresh, `pageView`, the `gpt` adapter (with a `googletag` stub), the `iframe` adapter with the existing systems |
252
+ | `test/formats.mjs` | `native`, `video` (muted autoplay when visible, pause, quartiles once each, sound button), `UALExt.track` and `sticky`, in Chromium with a real video file; plus the validation rules |
199
253
  | `test/package.mjs` | the package: import in Node (ESM and CJS) without `window`, server rendering of React and Vue, `exports` map, tarball contents (`npm pack`), types (correct usages compile, wrong ones fail) |
200
254
  | `test/wrappers.mjs` | the components in a real browser, with server rendering and delayed hydration: React (in the bundle, from CDN, Next version), Vue, **Next.js 16** (App Router, production build) and **Astro 7** (View Transitions); plus a check that demonstrates the problem the components avoid |
201
255