abmeter
Version:
ABMeter browser SDK — feature flags and A/B experiments with server-side pre-evaluated assignments
97 lines (68 loc) • 5.55 kB
Markdown
# ABMeter JavaScript SDK
[ABMeter](https://abmeter.ai) is a feature-flag and A/B-testing platform. You define parameters, experiments, and feature flags in the ABMeter Lab; this package reads the value assigned to each visitor in the browser and reports exposures and events back.
Unlike server-side SDKs, the browser never receives assignment rules, salts, or audience definitions. The SDK asks the ABMeter API for **pre-evaluated assignments** for the current user (`POST /api/v1/user-assignments`) and caches the resulting value map. Reading a value is a synchronous map lookup; an exposure is reported only when a value is actually read.
## Install
```bash
npm install abmeter
```
Or from the CDN (exposes `window.abmeter`):
```html
<script src="https://cdn.jsdelivr.net/npm/abmeter@0.2/dist/abmeter.iife.min.js"></script>
```
`@0.2` is a range: it picks up patch releases on its own. Pin the exact version
(`abmeter@0.2.2`) before production — a range still lets a release you did not
deploy reach your site, and only an exact version is served `immutable`. Avoid
the version-less form `npm/abmeter/dist/...` entirely: it follows every release,
breaking ones included. [unpkg](https://unpkg.com/abmeter/) serves the same file
at the same paths.
## Quick start
```js
import * as abmeter from 'abmeter';
abmeter.configure({
apiKey: 'YOUR_API_KEY',
baseUrl: 'https://abmeter.ai',
// Optional: identify a logged-in user. When omitted, the SDK generates a
// stable anonymous track id (a random UUID persisted in cookie + localStorage).
user: { userId: 'user_123', email: 'user@example.com' },
});
const buttonColor = abmeter.resolveParameter('button-color');
abmeter.trackEvent('purchase', { price: 4.99 });
```
`apiKey` must be a **publishable key** (`pk_...`, minted on the Lab API Keys page) — it is safe to embed in browser code and is limited server-side to the three endpoints this SDK uses. `configure` refuses any other key: secret keys (`api-...`) are never browser-safe.
`configure` hydrates synchronously from `localStorage` when a cached assignment map exists (no flash of defaults on repeat visits) and refreshes it in the background with ETag caching. `resolveParameter` returns the cached value and lazily queues an exposure — a value that is fetched but never read produces no exposure.
## Identity
- **Anonymous visitors** get a generated `track id` — a random UUID stored in a cookie and `localStorage` — sent as the `user_id`. Note that Safari caps JavaScript-set cookies at ~7 days; for long-running experiments, set the cookie server-side.
- **Logged-in users**: pass `user: { userId }` yourself. Keep one randomization unit per experiment — do not switch a user's id across the login boundary mid-experiment.
- `email` is optional and used only by email-predicate audiences.
### Decide the identity before the first `configure`
Calling `configure` again with a different `userId` is **not** a supported way to
upgrade an anonymous visitor to a logged-in one. Two things change that you cannot
undo:
- **The visitor may flip variant.** Assignments are fetched per user id, so the second
`configure` gets a different map. Whatever the page already rendered was for the old
identity.
- **Attribution splits.** Exposures already recorded carry the old id, everything after
carries the new one, and results match events to a visitor by the id their exposure was
recorded under. The two halves never meet — no error, just a metric quietly missing
conversions.
Queued telemetry itself is safe: `configure` drains the previous configuration in the
background rather than discarding it. Use `await reset()` first if you need certainty
that the drain completed before the page can navigate away.
This matters most in a single-page app served as static files, which cannot know the user
until an auth request returns. Two options that work:
- **Stay anonymous** for the life of the experiment and let the generated track id be the
randomization unit — correct, and it also measures logged-out traffic.
- **Configure once auth resolves**, and render defaults until then.
## Event submission
Exposures and events are queued and submitted in small batches in the background. The queue drains on `visibilitychange → hidden` and `pagehide` using `fetch(..., { keepalive: true })` with a `sendBeacon` fallback, so data survives tab closes without blocking navigation. Call `abmeter.flush()` on SPA route changes if you want an eager drain.
Content blockers may drop telemetry requests at the network layer; the SDK treats that as expected loss and never throws.
## API
| Function | Description |
| --- | --- |
| `configure(options)` | Initialize the SDK. Options: `apiKey` (required), `baseUrl`, `user: { userId?, email? }`, `flushInterval` (ms, default 1000), `logger`, `errorCallback`. |
| `resolveParameter(slug)` | Resolved value for this user, or `undefined` if unknown. Queues an exposure lazily (deduplicated over a 10-minute window). |
| `getExposure(slug)` | The exposure metadata for a parameter (`null` for feature-flag/default resolutions), without queueing anything. |
| `trackEvent(eventSlug, customFields?)` | Queue an event for the configured user. |
| `flush()` | Drain the queue now (returns a promise). |
| `reset(options?)` | Drain fully and tear down timers/listeners. `configure` again to restart. |
All read/track functions are error-safe: failures are logged (and passed to `errorCallback` when configured) and return a safe default instead of throwing.