@gethamla/sdk
v2.21.0
Published
Lightweight marketing SDK for any business website — popups, push notifications, email campaigns, and behavioral targeting. Works with Salla, Zid, and custom platforms.
Maintainers
Readme
@gethamla/sdk
The thin client for Hamla — an AI marketing team that plugs into whatever a business already uses.
This package is the browser-side runtime. It loads on a merchant's website, fires triggers, tracks events, and renders on-site content (popups, toasts, banners). The server is the brain — campaign selection, channel/step picking, frequency, fatigue, variable resolution, off-site channel scheduling, and intelligence (intent / journey / fatigue) all happen server-side. The SDK is a lookup-and-render shell.
For agent context and the full architectural rationale, see CLAUDE.md.
🚀 Quick Start
Installation
pnpm add @gethamla/sdk
# or npm install @gethamla/sdkDrop-in script (recommended for merchants)
<script>
!function(w,d){
w.Hamla=w.Hamla||{};
w.Hamla.config={businessId:"your-business-id"};
var s=d.createElement("script");
s.async=1;
s.src="https://cdn.hamla.io/sdk/v5/hamla.min.js";
d.head.appendChild(s);
}(window,document);
</script>ES module
import { HamlaSDK } from '@gethamla/sdk';
const hamla = new HamlaSDK({
businessId: 'your-business-id',
debug: process.env.NODE_ENV === 'development',
});
await hamla.init();After init(), the SDK calls /api/sdk/init, receives a per-page set of resolved render instructions keyed by trigger, prefetches the render chunk in the background, and arms the trigger engine. There's nothing else for the merchant to wire up.
🏗️ Architecture (v6 thin client)
Two-phase loader keeps the always-loaded payload small.
| Phase | File | Loaded | Purpose |
|-------|------|--------|---------|
| Loader | sdk.js (~15-18 KB gzipped) | Immediately on page load | init, triggers, event tracking, MicroAdapters, LiveSlots, ChunkLoader |
| Render chunk | sdk-render.js (~20-25 KB) | Async via script injection (prefetched after init) | Popups, toasts, banners |
Page loads → sdk.js (loader) loads
→ MicroAdapter detects platform + collects pageContext + cart
→ POST /api/sdk/init with pageContext + cart
→ Server returns fully resolved instructions + liveSlots config
→ ChunkLoader prefetches sdk-render.js in background
→ TriggerEngine arms triggers
Trigger fires → LiveSlots resolves 4 cart vars client-side
→ ChunkLoader.getRender() (chunk already prefetched, 0ms)
→ Show popup/notification instantly
→ EventTracker reports event async to /api/sdk/eventsComponents
| Component | File | Purpose |
|-----------|------|---------|
| HamlaSDK | src/HamlaSDK.ts | Entry point — owns init, trigger handling, event reporting |
| TriggerEngine | src/triggers/TriggerEngine.ts | Detects DOM events (exit intent, scroll, time, idle, click) using PageContext |
| InstructionExecutor | src/orchestration/InstructionExecutor.ts | Trigger key → render instruction lookup (pure lookup, no scoring) |
| RenderEngine | src/rendering/RenderEngine.ts | Lives in the render chunk; renders popups and notifications |
| EventTracker | src/analytics/EventTracker.ts | Batched, async reporting to /api/sdk/events |
| MicroAdapter (Salla / Zid) | src/adapters/SallaMicroAdapter.ts, src/adapters/ZidMicroAdapter.ts | Slim platform adapters (~150 lines each) for events, CTAs, cart, page context |
| LiveSlots | src/slots/LiveSlots.ts | Resolves the 4 real-time cart variables client-side at render time |
| ChunkLoader | src/chunks/ChunkLoader.ts | Async render chunk loading + prefetch |
What the SDK does NOT do
The server owns all of this:
- Campaign selection (highest priority per trigger)
- Channel / step selection (flow position from event history)
- Frequency enforcement (
once_per_session,once_per_day, etc.) - Conversion suppression (
stopOnConversion) - Session fatigue limits (
maxOnSiteMessages) - Intelligence scoring (intent, journey, fatigue)
- Off-site channel delivery (email, browser push) and scheduling
- Variable resolution — the server resolves 28 of 35 template variables; LiveSlots only handles the 4 real-time cart vars
📡 Channels
| Channel | Status |
|---------|--------|
| On-site popup | ✅ Shipped (render chunk) |
| On-site notification / toast / banner | ✅ Shipped (render chunk) |
| Email | ✅ Shipped (server-side, scheduled via ScheduledJob) |
| Browser push | ✅ Shipped (apps/push-frame + server) |
| SMS | ⏳ Coming — server-side stub, not yet implemented |
| WhatsApp messaging | ⏳ Coming — server-side stub, not yet implemented |
The SDK exposes the same trigger / event surface for all of them — the server decides whether to render an on-site channel inline or schedule an off-site one.
🔌 SDK API
Initialization
import { HamlaSDK } from '@gethamla/sdk';
const hamla = new HamlaSDK({
businessId: 'business_123',
apiUrl: 'https://api.hamla.io', // optional
debug: true, // enable console logging
});
await hamla.init();Identify a visitor
hamla.identify({
userId: 'user_456',
email: '[email protected]',
phone: '+966501234567',
attributes: { firstName: 'Sarah', segment: 'vip', lifetimeValue: 5000 },
});Update cart
hamla.updateCart({
items: [{ productId: 'prod_1', name: 'iPhone 15', price: 3999, quantity: 1 }],
total: 3999,
currency: 'SAR',
});Track events
hamla.track('page_view', { pageType: 'product', productId: 'prod_1' });
hamla.track('video_watched', { videoId: 'intro_video', duration: 120 });Manual trigger
hamla.trigger('custom_event', { customData: 'value' });Test rendering (development only)
hamla.showNotification({
type: 'toast', // or 'banner', 'popup'
message: { type: 'simple', text: 'Test notification' },
position: 'bottom-right',
dismissAfter: 5000,
});Listen to SDK events
hamla.on('notification:shown', (e) => console.log('shown', e));
hamla.on('notification:dismissed', (e) => console.log('dismissed after', e.durationShown));
hamla.on('cta:clicked', (e) => console.log('cta clicked', e.ctaLabel));
hamla.on('conversion:tracked', (e) => console.log('conversion', e.value));🔑 Trigger keys
The SDK and server agree on one canonical key format.
| Trigger | Key | Example |
|---------|-----|---------|
| exit_intent | exit_intent | exit_intent |
| time_on_page | time_on_page:{seconds} | time_on_page:5 |
| scroll_depth | scroll_depth:{percent} | scroll_depth:75 |
| idle | idle:{seconds} | idle:10 |
| click | click:{encodedSelector} | click:%23buy-btn |
| view_product | view_product | view_product |
| add_to_cart | add_to_cart | add_to_cart |
| cart_abandoned | cart_abandoned:{seconds} | cart_abandoned:300 |
⚡ Performance
| Metric | Target | |--------|--------| | Loader bundle (gzipped) | ~15-18 KB | | Render chunk (gzipped) | ~20-25 KB (lazy) | | Decision time at trigger | < 5 ms (pure lookup) | | Layout shift (CLS) | 0 | | Render isolation | Shadow DOM |
🌍 Browser support
- Chrome / Edge: last 2 versions
- Firefox: last 2 versions
- Safari: 14+
- Mobile Safari: iOS 14+
- Android WebView: Android 8+
🔒 Privacy & security
- First-party storage only — no third-party cookies
- All API calls over HTTPS
- Content rendered through Shadow DOM with sanitization
- Compatible with strict CSP
- Consent / privacy is not the SDK's job — merchant CMPs block the script at load time; off-site consent is enforced server-side per contact
📚 Algorithm references
For the math behind orchestration, attribution, and analytics, see ./docs/algorithms/.
🤝 Contributing
See CONTRIBUTING.md. Before changing SDK behavior, re-read CLAUDE.md — there's a hard list of "never add this to the client" rules that have already cost us thousands of lines of deleted code (orchestration engine, message selector, variable resolver, consent manager).
📄 License
MIT — see LICENSE.
