Node.js (server)
Zero runtime dependencies (uses the built-in global fetch). TypeScript-first, ships types.
| Package | @theaimart/adx-node |
|---|---|
| Language | TypeScript |
| Distribution | npm |
| Requirements | Node.js >= 18 (global fetch; >= 20 recommended). |
Install
npm
bash
npm install @theaimart/adx-nodeUsage
Express handler serving a real end user
ts
import { Adx } from '@theaimart/adx-node';
const adx = new Adx({ apiKey: 'pk_your_publisher_key' });
app.get('/ad', async (req, res) => {
const ad = await adx.requestAd({
slotId: '8b1f2c3d-....-uuid',
pageUrl: `https://yoursite.com${req.originalUrl}`,
// Forward the END USER's context — otherwise every impression looks like one
// server IP + UA and gets fraud-scored to a no-fill:
userAgent: req.headers['user-agent'],
clientIp: req.ip,
});
if (!ad.filled) return res.status(204).end();
res.send(Adx.renderHtml(ad));
// Later, when your frontend reports the ad was >=50% visible for >=1s:
if (ad.isTrackable) await adx.reportViewable(ad.impId!);
});Production hardening
- SQL-filter-safe encoding of free-text params so a normal pageUrl never trips the backend WAF.
- Fraud-safe User-Agent: ships theaimart-adx-node/<ver>, never a bot-blocklisted UA — and lets you forward the real end-user UA/IP.
- 4-way union parsing normalizes internal / openrtb / house / no-fill into one Ad.
- Fail-closed: any error, timeout, or non-200 -> ad.filled === false, never a throw (unless raiseOnError: true).
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 |
|---|---|
new Adx({ apiKey, baseUrl?, timeoutMs?, userAgent?, transport?, raiseOnError? }) | Construct a client. |
adx.requestAd({ slotId?, slotName?, pageUrl?, pageKeywords?, usPrivacy?, euconsentV2?, userAgent?, clientIp?, identityCookie? }) | Returns Promise<Ad>. |
adx.reportViewable(impId, { userAgent?, clientIp? }?) | Returns Promise<boolean>. |
adx.clickUrl(impId) | Builds a click-tracker URL (rarely needed directly). |
Adx.renderHtml(ad, cssClass?) | Renders an Ad to an HTML snippet, escaping all interpolated values. |
The normalized Ad model
{ filled, kind ('image'|'html'|'no_fill'), source ('internal'|'openrtb'|'house'), impId, identityId, creative, auction, reason, retryAfterSeconds, isTrackable, raw }. The Transport interface is injectable — swap fetch for undici.request, or mock it in tests.
Build & test
bash
cd packages/node
npm install # dev deps only (typescript, @types/node)
npm test # node --test test/*.test.ts (runtime, via native TS strip)
npm run typecheck # tsc --noEmit
npm run build # tsc -> dist/ (.js + .d.ts)The test suite runs with no third-party packages on Node ≥ 23.6 (native type stripping), and includes an end-to-end test against a local mock server reproducing the backend WAF.