@purplle/beacon-web-sdk
v0.1.0
Published
Beacon Web SDK for sending batched clickstream events to the Beacon Event Gateway.
Downloads
147
Maintainers
Readme
Beacon Web SDK
Thin browser SDK for sending batched clickstream events to the Beacon Event Gateway. Events are queued locally, batched, and sent with retries and offline support.
Table of contents
- Install
- Quick start
- API reference
- Configuration
- Event shape and strict typing
- How it works
- Queues and persistence
- Retries and HTTP status handling
- Page lifecycle (visibility & unload)
- Environments and endpoints
- Example and local development
Install
Using npm (once published):
npm install @purplle/beacon-web-sdkFor local development in this repo:
npm install
npm run buildQuick start
import { Beacon } from '@purplle/beacon-web-sdk';
// Prefer env so the SDK picks the right endpoint
Beacon.init({
env: 'production',
token: '<platform-token>',
visitorId: '<anonymous-id>',
modeDevice: 'desktop',
});
Beacon.track(Beacon.createEvent('add_to_cart', {
target_entity_id: 1001,
target_entity_type: 'PRODUCT',
quantity: 1,
our_price: 1499,
}), {
page_name: document.title,
page_url: window.location.href,
});Optional: manual flush, monitoring, and queue tuning:
Beacon.init({
env: 'production',
token: '<platform-token>',
batchSize: 20,
flushIntervalMs: 10_000,
maxQueueSize: 1000,
failedBatchTtlMs: 24 * 60 * 60 * 1000,
onBatchSent: (payload) => { /* monitor */ },
onBatchFailed: (payload) => { /* alert */ },
});
Beacon.track(Beacon.createEvent('interaction', {
target_entity_id: 'checkout',
target_entity_type: 'BUTTON',
}), { page_url: window.location.href });
await Beacon.flush(); // optional: force send nowAPI reference
| Method | Description |
|--------|-------------|
| Beacon.init(config) | Initialize the SDK. Requires token; use env so the SDK resolves the ingestion endpoint. Call once (e.g. at app bootstrap). |
| Beacon.createEvent(eventName, payload) | Build a schema-validated event payload from generated types. |
| Beacon.track(event, pageData?) | Enqueue a prepared event from createEvent. Non-blocking; events are batched and sent in the background. |
| Beacon.flush() | Manually trigger a flush (main queue + failed-batch queue). Returns a Promise<void>. |
Exported types: BeaconInitConfig, BeaconWebSDKConfig, BeaconTrackEvent, BeaconTrackPageData, BeaconTrackEventName, BeaconTrackEventByName, BeaconCreateEventInput, BeaconPreparedEvent, BeaconEvent, BeaconBatchPayload, EventData, PageData, and all generated event interfaces (for example AddToCartEvent).
Configuration
All options under Beacon.init(config).
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| env | string | 'local' | Environment name; SDK resolves endpoint from this (local, sandbox, test, production or custom). |
| token | string | required | Platform token; sent as token header. |
| visitorId | string | — | Anonymous visitor id; sent as visitorppl header and used in batch_id. |
| modeDevice | string | 'desktop' | Device mode; sent as mode_device header and in context. |
| batchSize | number | 20 | Max events per batch (cap 50). Flush is triggered when the queue reaches this size. |
| flushIntervalMs | number | 10_000 | Max time (ms) between automatic flushes. |
| maxQueueSize | number | 1000 | Max events in the main queue; oldest are dropped (FIFO) when exceeded. |
| maxQueueBytes | number | 5 * 1024 * 1024 | Approx max size (bytes) of the main queue; oldest events evicted when exceeded. |
| failedBatchTtlMs | number | 24 * 60 * 60 * 1000 (24h) | TTL for failed batches. Batches older than this are removed from the failed-batch queue. |
| maxEventAgeMs | number | — | If set, events older than this (ms) are dropped before send. Omit for no age limit. |
| schemaVersion | string | '2.3' | Schema version sent in the payload. |
| sdkVersion | string | 'web-1.0.0' | SDK version in context. |
| appVersion | string \| null | — | App version in context. |
| buildNumber | string | '1' | Build number in context. |
| debug | boolean | false | When true, logs payloads and skips all network calls. |
| onEventDropped | (event) => void | — | Called when an event is dropped (queue full or stale). |
| onBatchSent | (payload) => void | — | Called when a batch is successfully sent. |
| onBatchFailed | (payload) => void | — | Called when a batch is moved to the failed-batch queue after all retries. |
Event shape and strict typing
Beacon.createEvent(eventName, payload) + Beacon.track(event, pageData?) is strongly typed:
eventNamemust be one of generated event names.payloadmust match the exact generated schema for that event.trackaccepts only prepared events fromcreateEvent.pageDatais optional and must matchPageData.- invalid keys or wrong value types are rejected at compile time by TypeScript.
Example:
import { Beacon } from '@purplle/beacon-web-sdk';
const event = Beacon.createEvent('add_to_cart', {
target_entity_id: 1001,
target_entity_type: 'PRODUCT',
quantity: 2,
our_price: 1499,
});
Beacon.track(event, { page_url: window.location.href });How it works
- Enqueue —
Beacon.track()appends events to the main queue (in-memory +localStorage). - Flush triggers — A flush runs when:
- The queue size reaches
batchSize, or - The periodic timer fires every
flushIntervalMs(e.g. 10s), or - The app goes online (after being offline), or
- The tab becomes hidden (
visibilitychange), or - You call
Beacon.flush().
- The queue size reaches
- Send — Batches are sent with retries (see Retries and HTTP status handling). On success, the batch is removed from the queue and
onBatchSentis called. - On failure after retries — The batch is moved to the failed-batch queue (dead-letter queue), persisted in
localStorage, andonBatchFailedis called. The failed-batch queue is retried on the same 10s flush cycle with exponential backoff until success or TTL.
Queues and persistence
| Queue | Storage key | Purpose |
|-------|-------------|---------|
| Main event queue | beacon_event_queue | Pending events; FIFO; limited by maxQueueSize and maxQueueBytes. |
| Failed-batch queue | beacon_failed_batch_queue | Batches that failed after all immediate retries; retried with backoff; evicted after failedBatchTtlMs. |
- Both use
localStoragewhen available (e.g. browser). Data survives refresh and is sent when the app is back online or on the next flush. - There is no time-based TTL on the main queue; only count and size limits. Use
maxEventAgeMsto drop very old events before send.
Retries and HTTP status handling
HTTP status behaviour
| Status | Treated as | Behaviour |
|--------|------------|-----------|
| 2xx | Success | Batch removed from queue; onBatchSent called. |
| 500, 502, 503, 429 | Retryable | Retried with exponential backoff; after all retries, batch goes to failed-batch queue. |
| 400, 401, 403 | Non-retryable | No retries; batch moved to failed-batch queue immediately. |
| Other | Non-retryable | Same as above. |
The SDK honours the Retry-After response header when present for retryable statuses.
Main queue retry (per batch)
- Each batch is sent through a RetryManager: up to 4 attempts (1 initial + 3 retries).
- Backoff between attempts: 2s → 4s → 8s (capped at 16s), or
Retry-Afterif provided. - If all 4 fail: batch is moved to the failed-batch queue and
onBatchFailedis called.
Failed-batch queue retry
- Failed batches are stored with a
failedAttimestamp andretryCount. - On each flush cycle (every ~10s), batches that are due (based on backoff) are tried again.
- Per attempt: same RetryManager (4 attempts, 2s/4s/8s backoff).
- Between attempts: exponential backoff 30s → 1m → 2m → … (capped at 1h). So the next “due” time is
failedAt + min(30s × 2^retryCount, 1h). - Batches older than
failedBatchTtlMs(default 24h) are removed and no longer retried.
Page lifecycle (visibility & unload)
- Tab hidden (
visibilitychange→document.visibilityState === 'hidden'): the SDK triggers a normalflush()so pending events are sent withfetchbefore the user leaves. - Page unload (
beforeunload): the SDK sends one batch vianavigator.sendBeacon()so some events can still be delivered when the tab is closed. Custom headers (e.g.token) are not sent withsendBeacon; your backend may need to accept beacon requests without them or support token in body for that path. IfsendBeaconfails, that batch is re-queued and will be sent on the next load.
Environments and endpoints
Pass env to Beacon.init(); the SDK resolves the ingestion URL internally.
| env | Endpoint |
|-------|----------|
| local | http://localhost:8080/de/events/v1/push |
| sandbox | https://sandbox.purplle.com/de/events/v1/push |
| test | https://test.purplle.com/de/events/v1/push |
| production | https://www.purplle.com/de/events/v1/push |
Unsupported env values throw at init. Extend resolveEndpointFromEnv in the SDK (or use a wrapper) to add more environments.
Example and local development
A minimal demo lives under example-apps/ (e.g. React). To run it:
Build the SDK:
npm install npm run buildServe the repo over HTTP (from project root):
npx serve .Open the example (e.g.
http://localhost:3000/beacon-web-sdk/example-apps/react/) and trigger events. Batched requests go to the endpoint for yourenv(e.g.http://localhost:8080/de/events/v1/pushforlocal).
Summary
- Init with
envandtoken; track with typed generated event objects and optionalpageData. - Events are queued in
localStorage, batched (size + 10s interval), and sent with retries. - Failed batches go to a dead-letter queue and are retried with backoff for up to 24h (configurable).
- Tab hidden and page unload trigger an extra flush and a best-effort
sendBeaconfor better delivery.
