Web (Browser)
Zero-dependency browser SDK with an auto-rendering banner API and a framework-agnostic client.
| Package | @theaimart/adx-web |
|---|---|
| Language | TypeScript |
| Distribution | npm / CDN |
| Requirements | Any modern browser (evergreen Chrome/Firefox/Safari/Edge). |
Install
npm
npm install @theaimart/adx-webScript tag (auto-attaches window.TaimAds)
<script src="https://cdn.jsdelivr.net/npm/@theaimart/adx-web/dist/taimads.global.js"
data-api-key="pk_your_publisher_key"></script>Usage
Auto-rendering banner
<div id="taim-ad-8b1f2c3d"></div>
<script type="module">
import { TaimAds } from '@theaimart/adx-web';
TaimAds.init({ apiKey: 'pk_your_publisher_key' });
TaimAds.createBanner({
slotId: '8b1f2c3d-....-uuid', // container defaults to #taim-ad-<slotId>
lazy: true, // request only when near viewport
refresh: 60, // auto-refresh seconds (clamped to >=30)
onLoad: (ad) => console.log('filled', ad.source),
onError: (err) => console.log('no fill', err.message),
});
</script>Framework-agnostic client (React/Vue/custom)
import { AdxWebClient, AdKind } from '@theaimart/adx-web';
const client = new AdxWebClient({ apiKey: 'pk_your_publisher_key' });
const ad = await client.requestAd({ slotId: '8b1f-uuid', pageUrl: location.href });
if (ad.filled) {
// render ad.creative yourself (img+anchor, or sandboxed iframe for ad.kind === AdKind.Html)
if (ad.isTrackable) await client.reportViewable(ad.impId!);
}adx.theaimart.co/theaimart.co. To serve on arbitrary publisher domains the backend must widen CORS or front /api/v1/serve/* with an origin-reflecting edge. Native mobile/server SDKs are unaffected.Production hardening
- SQL-filter-safe encoding of pageUrl/keywords/consent so real URLs never trip the backend WAF.
- Header-less simple GET with credentials: 'include' — no custom header means no CORS preflight; the _an_id identity cookie flows automatically.
- Fail-closed: any error/non-200 → no-fill; ads never throw into your page.
- Beacons use navigator.sendBeacon (survives teardown), falling back to keepalive fetch.
These behaviors come directly from the wire contract — see User-Agent rules and WAF-safe encoding for why they matter. Not getting fills? See Troubleshooting.
API
| Member | Notes |
|---|---|
TaimAds.init({ apiKey, baseUrl? }) | Configure the global auto-render controller once. |
TaimAds.createBanner({ slotId, container?, lazy?, refresh?, onLoad?, onError? }) | Render a banner into a container (defaults to #taim-ad-<slotId>). |
new AdxWebClient({ apiKey, baseUrl? }) | Framework-agnostic client for custom rendering. |
client.requestAd({ slotId?, slotName?, pageUrl?, pageKeywords?, usPrivacy?, euconsentV2? }) | Request an ad decision — returns a normalized Ad. |
client.reportViewable(impId) | Fire the viewability beacon. |
isSafeHttpUrl(url) | The same http(s)-only scheme check the SDK uses internally before turning a click_url into a link — exported for custom renderers. |
The normalized Ad model
AdKind (Image/Html/NoFill), AdSource (Internal/OpenRtb/House), plus the shared filled/impId/creative/auction/isTrackable fields common to every SDK.
Advanced
Beyond the core v1 contract, this SDK ships additional client-side capabilities:
Mediation (waterfall + bidding across networks)
MediationController fetches a publisher's mediation chain from the public config endpoint and runs it: taimads first-party demand and any bidding adapters compete in parallel on price; if nothing bids, waterfall adapters are called in priority order until one fills. External networks (AdMob, Meta, …) ship their own proprietary SDKs the package can't bundle, so integrators register an AdapterFn per network that wraps that network's SDK — taimads itself is always the built-in bidding entry, no adapter needed.
VAST video playback
parseVast understands the VAST 2.0/4.0 documents the ad server emits (a single InLine linear ad) — it is deliberately not a general-purpose VAST parser. playVastAd wires the result onto an HTML5 <video> element with IAB-correct beaconing (impression fires once on first play).
On-device persona (privacy-preserving contextual targeting)
recordTopic/topTopics count interest topics in the user's own localStorage — nothing is uploaded. At request time, the top topics ride the serve call as plain contextual keywords: no identifier, no profile, nothing joinable server-side. clearPersona lets the user wipe it entirely.
Ad inspector overlay
enableInspector() drops a floating debug panel listing every ad the page rendered — network, impression id, clearing price — with one-click links to the public nutrition label at /why/{imp_id} where the signed receipt proves the money trail. Zero dependencies, inline styles only, safe to ship in production behind ?adx_debug=1.
Build & test
cd packages/web
npm install
npm test # node --test (core + encoding logic)
npm run typecheck # tsc --noEmit
npm run build # tsc -> dist (ESM + types)
npm run build:cdn # esbuild -> dist/taimads.global.js (IIFE for <script>)