@afrokala/ads-sdk
v0.1.0
Published
Afrokala Ads SDK — core + vanilla client. Transport, caching, event outbox, DOM renderers for banner/interstitial/inline/rewarded ad units.
Readme
@afrokala/ads-sdk
Core + vanilla client for the Afrokala Ads network. Framework-free TypeScript: transport
(requestAd fetch + retry/backoff), a serve-token store, prefetch + LRU cache with TTL, an event
outbox (idempotency keys, batching ≤500, retry, localStorage persistence for offline), and DOM
renderers for all four ad unit types.
Status: core landed (sdk-002); CDN/IIFE auto-mount hardened (sdk-005).
AdsClientimplements the anonymous-device transport (requestAdwith timeout + bounded retry/backoff), a TTL/LRU decision cache, the idempotent event outbox (postClientAdEvents, persisted, dedupe-by-serve), viewability impression counting, and the four DOM renderers. Framework wrappers (React landed, Vue/Angular follow in sdk-006/007).
Install
pnpm add @afrokala/ads-sdkOr via CDN — see docs/vanilla.md for the full vanilla/CDN quick-start (script tag attributes, ad-unit data attributes, manual init for SPA timing, bundle-size budget):
<script src="https://unpkg.com/@afrokala/ads-sdk/dist/ads-sdk.global.js" data-app-id="your-app-id"></script>
<div data-afk-unit="home_banner"></div>Quick start
import { AdsClient } from "@afrokala/ads-sdk";
const client = new AdsClient({
appId: "your-app-id",
endpoint: "https://serve.afrokala.example", // serving base (requestAd / postClientAdEvents)
onOpenUrl: (url) => window.open(url, "_blank", "noopener"),
});
client.mountBanner(document.querySelector("#banner")!, "home_banner");
client.mountInline(document.querySelector("#feed")!, "feed_slot", { mode: "block" });
await client.showInterstitial("interstitial_unit");
const rewarded = client.loadRewarded("rewarded_unit");
rewarded.onReward((meta) => {
// ADVISORY only — surface a "reward pending" UX. Server-Side Verification (SSV) is the sole
// authoritative grant path; never credit a user on onReward alone.
});
// when rewarded.state === "loaded", rewarded.serveId carries the exact serve's `serveId` claim
// (null before load / after a no-fill) — an attribution HINT for binding a reward intent to this
// serve, not proof: SSV remains the only money-grade signal.
rewarded.show();Privacy + invariants
- No PII. The only identifier is an anonymous, regenerable
deviceIdUUID kept in localStorage. - An ad error never breaks the host app. No-fill and network failure both resolve to a silent
no-ad; every host callback (
onError/onOpenUrl/onReward/onClose) is invoked defensively. - Rewards are advisory.
onRewardfires on client-side completion as a UX hint; SSV (net-004) is the only path that actually grants a reward.
Contracts
src/contracts/ holds the wire schemas (getCampaignsForApp response, postAdEvents
request/response) copied from afrokala/functions/src/serving/contract.ts — the
contracts-not-code rule. src/contracts/fixtures/ holds real emulator-shaped payloads;
src/contracts/contracts.test.ts round-trips every fixture through its schema.
contract.lock.json pins a sha256 hash of primitives.ts + get-campaigns.ts +
post-ad-events.ts. pnpm check:contract-drift (wired into CI) recomputes the hash and fails the
build if it no longer matches — catching an edit to the SDK's copy that wasn't first mirrored on
the afrokala side.
To deliberately re-pin after a real, mirrored contract change:
pnpm --filter @afrokala/ads-sdk check:contract-drift -- --writeScripts
| Script | What it does |
|---|---|
| build | tsup — emits ESM + CJS (dist/index.{js,cjs}) and an IIFE CDN bundle (dist/ads-sdk.global.js, from src/cdn-entry.ts) |
| test | vitest run |
| typecheck | tsc --noEmit |
| lint | eslint . |
| check:contract-drift | Hashes src/contracts/*.ts against contract.lock.json |
| check:bundle-size | Gzips dist/ads-sdk.global.js and fails if it exceeds the 30 KiB budget |
Vanilla / CDN
src/index.ts (ESM/CJS) stays side-effect-free; src/cdn-entry.ts is the separate, side-effecting
entry tsup builds into the IIFE — it boots auto-mount on load and is what window.AfrokalaAdsSDK
resolves to on the CDN build. src/cdn.ts holds the testable bootstrap logic (init/boot):
auto-mount discovery, a MutationObserver for SPA-safe late-added slots, a multiple-<script
data-app-id>-tag guard, and an idempotent init() so a second call/tag never spins up a second
AdsClient. Full quick-start: docs/vanilla.md.
