@dobu/sdk
v0.2.0
Published
Universal SDK for Dobu Mergen: browser tracker, edge-safe server client, React hooks, and Next.js integration.
Downloads
230
Maintainers
Readme
@dobu/sdk
Browser SDK for the Dobu Mergen recommendation engine.
Tracks views, clicks, impressions, cart events, and search; stitches anonymous
sessions to logged-in users; attaches request_id attribution so conversions
flow back to the model that served them.
- Size: ~4 kB gzipped (IIFE min bundle)
- Runtime: evergreen browsers (ES2020). Node 18+ only for types/bundling.
- Storage:
localStoragefor ids, IndexedDB for offline replay (both optional). - Transport: batched
fetchwithsendBeaconon page-hide.
Install
npm install @dobu/sdk
# or
pnpm add @dobu/sdk
# or
yarn add @dobu/sdkOr drop it in with a <script> tag — see Script tag below.
Quick start
import * as dobu from "@dobu/sdk";
dobu.init({
key: "pub_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
projectId: "shoppy",
endpoint: "https://dobu.example.com", // optional; defaults to the hosted Dobu
});
dobu.page({itemId: "product-123"});
dobu.track("click", {itemId: "product-123", position: 0});
dobu.identify("user_42");Publishable keys (pub_…) are safe to ship to the browser: they are
origin-locked and cannot emit server-only event types (purchase, rate,
refund).
Using with Next.js / React
@dobu/sdk is a browser-only SDK. It exports are safe to import from a
server component or RSC boundary — init() / track() / identify() are
no-ops when window is unavailable, so SSR won't crash — but no event will
actually be sent from the server. Always call the SDK from a client
component:
// app/DobuProvider.tsx
"use client";
import {useEffect} from "react";
import * as dobu from "@dobu/sdk";
export function DobuProvider() {
useEffect(() => {
dobu.init({
key: process.env.NEXT_PUBLIC_DOBU_KEY!,
projectId: "shoppy",
autoTrackImpressions: true,
});
}, []);
return null;
}// app/layout.tsx
import {DobuProvider} from "./DobuProvider";
export default function RootLayout({children}: { children: React.ReactNode }) {
return (
<html>
<body>
<DobuProvider/>
{children}
</body>
</html>
);
}Tracking in a page component:
"use client";
import {useEffect} from "react";
import * as dobu from "@dobu/sdk";
export default function ProductPage({sku}: { sku: string }) {
useEffect(() => {
dobu.page({itemId: sku});
}, [sku]);
return <>...</>;
}Notes:
- Put the publishable key in
NEXT_PUBLIC_DOBU_KEYso Next ships it to the browser.pub_…keys are safe to expose — they are origin-locked and cannot emitpurchase/rate. - React Strict Mode double-invokes
useEffectin dev.dobu.init()is idempotent (re-init flushes the previous transport viasendBeaconbefore swapping), so this is harmless — just noisy in the network tab. - Call
dobu.flush()before a programmaticrouter.push(...)if you want deterministic delivery before navigation.
Script tag
<script async
src="https://dobu.example.com/sdk/v1/dobu.min.js"
data-project="shoppy"
data-key="pub_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
data-auto-impressions="true"></script>The SDK auto-initializes from data-* attributes and is globally available as
window.dobu.
API
init(config)
| Option | Type | Default | Notes |
|------------------------|-----------|------------------------|------------------------------------------------------------------------|
| key | string | — | Required. Must start with pub_. |
| projectId | string | — | Required. |
| endpoint | string | https://dobu.cody.mn | Your Dobu server. |
| flushIntervalMs | number | 2000 | Max delay between flushes. |
| maxBatchSize | number | 20 | Flush when the queue reaches this size. |
| sessionIdleMs | number | 1_800_000 (30 min) | Idle window before session_id rotates. |
| offlineReplay | boolean | true | Persist envelopes to IndexedDB; replay on next init. |
| autoTrackImpressions | boolean | false | Fire impression for [data-dobu-item-id] elements in view. |
| autoCartAbandon | boolean | false | Fire cart_abandon on tab background after an uncompleted cart event. |
| debug | boolean | false | Mirror queue/flush activity to console.log. |
track(eventType, opts?)
dobu.track("click", {
itemId: "product-123",
position: 2,
value: 49.99,
currency: "USD",
sku: "red-M",
variantProperties: {color: "red", size: "M"},
properties: {shelf: "home_for_you"},
});Allowed event types: impression, view, click, add_to_cart,
cart_abandon, wishlist, search, custom. Server-only types
(purchase, rate, refund) throw synchronously — those must be sent from
your backend with a secret key.
request_id is auto-attached to attribution events (view, click,
impression, add_to_cart, wishlist) from the last recommendation response;
override per-call via opts.requestId.
page(opts?)
Shorthand for track("view", opts).
search(opts)
dobu.search({
query: "red shoes",
resultsCount: 12,
resultsShown: ["product-1", "product-2", "product-3"],
filters: {brand: "Nike", size: "M"},
});identify(userId)
Binds an anonymous browser session to a real user. Emits an identify record
that the server stitches into ClickHouse so pre-login events are attributed
post-hoc.
reset(opts?)
dobu.reset(); // clear user id + experiment, keep anon id
dobu.reset({rotate: true}); // also mint a new anon id (shared devices)disable() / enable()
Consent gate. disable() drops the in-memory queue and ignores subsequent
track/identify calls; enable() resumes.
setRequestId(rid) / setExperimentId(experimentId, variantKey?)
Attach attribution context for subsequent events.
applyRecommendationResponse(resp)
const body = await fetch(...).then(r => r.json());
dobu.applyRecommendationResponse(body);
// = setRequestId(body.request_id) + setExperimentId(body.experiment_id, body.model_used)flush()
await dobu.flush();Deterministic flush. Useful right before a programmatic navigation.
requestDelete(userId)
const ok = await dobu.requestDelete("user_42");GDPR erasure — POST /projects/{id}/ingest/forget/{userId}. Returns true on
2xx.
Session helpers
dobu.getAnonId(); // "anon_…"
dobu.getSessionId(); // "sess_…"
dobu.rotateSessionId(); // force a fresh sessiondebug()
Returns { endpoint, projectId, queued, pendingIdentify, disabled, lastBatch }.
Auto impressions
Opt in at init time, then mark recommendation slots:
<div data-dobu-item-id="product-1" data-dobu-position="0">…</div>One impression fires per (itemId, sessionId) after the element is >50%
visible for ≥1 s. Optional attributes: data-dobu-sku, data-dobu-currency,
data-dobu-value.
Offline replay
Outbound batches are persisted to IndexedDB before being sent and deleted on a
2xx response. On the next init(), anything stuck in the queue (network down,
tab crash) is replayed oldest-first. Stale entries (>7 days) are dropped.
Falls back to in-memory in environments without IndexedDB (Safari private
mode, SSR).
CSP
script-src https://dobu.example.com;
connect-src https://dobu.example.com;License
MIT — see LICENSE.
