import type { ErrorReport } from './errors/error-payload'; export declare const MAX_CHUNK_SIZE_BYTES: number; /** * Running byte total for the chunk being assembled. * * This replaces a per-event `isChunkSizeExceeded(currentChunk, event)` that * re-serialized and re-UTF8-encoded the ENTIRE accumulated chunk to decide * whether one more event fit. That made chunking O(N^2) in both time and * allocation for a chunk of N events, on the main thread, on every flush. * Measured on realistic rrweb DOM-mutation events (~424 B each): * * events | before | after * 200 | 55 ms | 1.5 ms * 500 | 358 ms | 3.8 ms * 1000 | 1374 ms | 7.0 ms * 2000 | 5504 ms | 13.8 ms * * The queue flushes at ~800-1000 events (see MAX_QUEUE_SIZE in tracker.ts), and * p95 sessions carry ~10k rrweb events, so the 1000-event row was the steady * state on a busy page. Every flush was a long task by Google's 50 ms * definition, which lands on INP — a metric this SDK also reports. * * Byte-exactness matters, because the chunk boundaries this picks must match * what the old full-measure produced or payload sizes shift. The total is * therefore built from the same pieces JSON.stringify emits: * * - `envelopeBytes` — `{"sessionId":"…","events":[]}` with an empty array. * - each event's own encoded length. * - one byte for the `,` separator before every event after the first. * * Verified against the previous implementation across 5 event-shape scenarios * (uniform small, uniform medium, mixed, few-huge, exact-boundary): identical * chunk boundaries in every case, no chunk over MAX_CHUNK_SIZE_BYTES. See * `__tests__/chunk-size.test.ts`, which pins that equivalence. */ export declare function createChunkByteCounter(sessionId: string): { /** Would adding `event` push the serialized chunk past the cap? */ wouldExceed(event: any): boolean; /** Account for an event that has been pushed onto the chunk. */ add(event: any): void; /** Start a fresh chunk. */ reset(events?: any[]): void; }; export declare function validateSingleEventSize(event: any, sessionId: string): void; export declare function splitLargeEvent(event: any, sessionId: string): any[]; export declare class HumanBehaviorAPI { private apiKey; private baseUrl; private monthlyLimitReached; private throttledUntil; private sessionId; private endUserId; private cspBlocked; private consecutiveFetchFailures; private retryQueue; private persistence; private requestTimeout; private currentBatchSize; private _isDrainingPersisted; constructor({ apiKey, ingestionUrl }: { apiKey: string; ingestionUrl: string; }); /** * Set session and user IDs for tracking context */ setTrackingContext(sessionId: string, endUserId: string | null): void; /** * Drain the durable (localStorage) event queue: resend each persisted batch * oldest-first and remove it only once the server confirms receipt. * * Safe to call repeatedly — a concurrency guard prevents overlapping drains, * and a batch is removed only after a confirmed send, so nothing is lost on * failure. We stop at the first failure to preserve ordering and to back off * instead of hammering a still-down server (the caller retries on the next * flush tick / `online` event). Unlike sendEventsChunked, the send here never * re-persists on failure — the batch is already durable in storage. */ flushPersistedEvents(): Promise; /** * Send a single persisted batch exactly once. Returns true only when the * server confirms receipt. Never re-persists on failure (the batch is * already durable in storage) — the caller keeps it queued for the next * drain, so this cannot duplicate the localStorage entry. */ private _sendPersistedBatch; /** * Internal method to send request (used by retry queue) */ private _sendRequestInternal; /** * Handle unload - send pending retries via sendBeacon */ unload(): void; private checkMonthlyLimit; private isThrottled; /** * A 429 is rate-limit throttling unless the body explicitly says the * monthly limit was hit. Throttling pauses sends for the server-provided * Retry-After window; persisted/queued events drain once it passes. */ private _apply429; init(sessionId: string, userId: string | null): Promise<{ sessionId: any; endUserId: any; }>; /** * Server detects IP from HTTP requests automatically */ sendEvents(events: any[], sessionId: string, userId: string): Promise; sendEventsChunked(events: any[], sessionId: string, userId?: string, windowId?: string, automaticProperties?: any): Promise; /** * Send a chunk of events with retry logic and 413 handling */ private _sendChunkWithRetry; /** * Persist events to storage for retry */ private _persistEvents; sendUserData(userId: string, userData: Record, sessionId: string, identityToken?: string | null): Promise; /** * Fire a tiny, fire-and-forget beacon to evict this session from the * dashboard's live-presence set. Called from the SDK's `pagehide` * handler. Pass the current `endUserId` (when known) so the server can * evict the user-keyed entry — multi-device/multi-tab presence is * folded onto a single entry on the server side. * * We intentionally do NOT await or check the response: the page is * unloading and the indicator is best-effort. If the beacon never * fires (older browsers, mobile force-quit, browser crash), the entry * still ages out of the live set within ~LIVE_WINDOW_MS on the server. */ sendSessionEndBeacon(sessionId: string, endUserId?: string | null): boolean; /** * Periodic presence ping. Fires from a setInterval inside the SDK * tracker (~30s while the tab is visible) so an idle tab — no DOM * mutations, no input, empty rrweb queue — still refreshes its score * in the dashboard's live set. Without this, a perfectly static page * with a still user would drop out at LIVE_WINDOW_MS even though the * tab is open. Fire-and-forget; failures are silent. */ sendHeartbeatBeacon(sessionId: string, endUserId?: string | null): boolean; sendBeaconEvents(events: any[], sessionId: string, userId?: string, windowId?: string, automaticProperties?: any, groups?: Record): boolean; sendCustomEvent(sessionId: string, eventName: string, eventProperties?: Record, endUserId?: string | null, eventId?: string): Promise; sendCustomEventBatch(sessionId: string, events: Array<{ eventName: string; eventProperties?: Record; eventId?: string; }>, endUserId?: string | null): Promise; sendCustomEventBatchBeacon(sessionId: string, events: Array<{ eventName: string; eventProperties?: Record; eventId?: string; }>, endUserId?: string | null): boolean; /** * Send console log (warn/error) to ingestion server */ sendLog(logData: { eventId?: string; level: 'warn' | 'error'; message: string; stack?: string; url: string; environment?: string | null; timestampMs: number; sessionId: string; endUserId: string | null; automaticProperties?: Record; }): Promise; /** * Send network error to ingestion server */ sendNetworkError(errorData: { requestId: string; url: string; method: string; status: number | null; statusText: string | null; duration: number; timestampMs: number; sessionId: string; endUserId: string | null; errorType: string; errorMessage: string | null; errorName?: string | null; startTimeMs?: number; spanName?: string; spanStatus?: 'error' | 'success' | 'slow'; attributes?: Record; automaticProperties?: Record; }): Promise; /** * Send a batch of tracing spans to the ingestion server. Fire-and-forget, * fails silently. Uses a plain `fetch` (the SDK's own request must not be * re-captured) and posts to the spans batch endpoint. */ sendSpans(spans: unknown[], ctx: { sessionId: string | null; endUserId: string | null; automaticProperties?: Record; }): Promise; /** sendBeacon variant of `sendSpans` for page unload (synchronous). */ sendSpansBeacon(spans: unknown[], ctx: { sessionId: string | null; endUserId: string | null; automaticProperties?: Record; }): boolean; /** * Send a captured crash/error report to the ingestion server. * * Routed through the retry queue (not a bare fire-and-forget `fetch`) so a * transient 5xx / network failure is retried with backoff instead of losing * the crash — and anything still queued is flushed via sendBeacon on unload. * The queue does not retry 4xx (except 408/429), which is the correct * behaviour for a malformed report. Retrying a duplicate is safe: the SDK * dedups within a window and the server groups by fingerprint. The queue * uses a plain `fetch` internally, so this request is never re-captured as a * network error. Best-effort and never throws into the host app. */ sendError(report: ErrorReport): Promise; /** * Trigger server-side GeoIP enrichment. Server resolves the IP from request * headers; result is published as a $geoip analytics event that updates * raw_sessions.country/city/region in ClickHouse. */ sendIpInfo(sessionId: string, endUserId: string | null): Promise; /** * Wrapper for fetch that tracks network errors and falls back to sendBeacon on CSP violations * Skips tracking for SDK's own requests to ingestion server */ private trackedFetch; /** * Fallback to sendBeacon when CSP blocks fetch * sendBeacon bypasses CSP connect-src restrictions * Note: sendBeacon is synchronous and fire-and-forget, so we can't await it */ private trackedFetchWithBeaconFallback; /** * Detect if an error is a CSP violation */ private isCSPViolation; /** * Check if network request should be skipped (SDK's own requests) */ private shouldSkipNetworkTracking; private classifyHttpError; private classifyNetworkError; } //# sourceMappingURL=api.d.ts.map