import * as react_jsx_runtime from 'react/jsx-runtime'; import React from 'react'; interface UTMParams { source?: string; medium?: string; campaign?: string; term?: string; content?: string; _custom?: boolean; } interface NohmoConfig { projectId: string; apiKey: string; flushInterval?: number; debug?: boolean; autoPageView?: boolean; autoScrollDepth?: boolean; autoTimeSpent?: boolean; autoCapture?: boolean; /** * Automatically capture uncaught JS errors, unhandled promise rejections, * failed network requests (fetch/XHR 4xx/5xx), and resource 404s as * JS_ERROR / HTTP_ERROR events. Defaults to true. Set false to disable. */ autoErrors?: boolean; /** * The build currently running, e.g. "2.4.1" or a commit SHA. Reported on init so * Nohmo can build a deploy timeline and line metric movements up against releases * ("conversions fell 18% — that lines up with 2.4.1 shipping"). Optional: without it * the timeline only contains releases reported by CI or added by hand. */ release?: string; /** * Custom URL parameter names to treat as attribution when no utm_* params * are present. Checked in order; the first match becomes source and the * param name itself becomes medium so it's identifiable in the dashboard. * Defaults to ['ref']. * Example: ['ref', 'reference', 'src', 'from'] */ attributionParams?: string[]; } interface NohmoEvent { deviceId: string; userId: string | null; sessionId: string; event: string; data: Record; page: string; referrer: string; ts: number; utm?: UTMParams; /** Which client library produced this event — 'web' | 'react-native' | 'flutter' | * 'server'. Distinct from platform: a Flutter app and a React Native app both * report platform 'android', and without this the server cannot tell them apart. */ sdk?: string; /** The SDK's own package version, stamped at build time. */ sdkVersion?: string; } interface NohmoUser { userId: string; email?: string; meta?: Record; } interface NohmoState { deviceId: string | null; userId: string | null; sessionId: string; ready: boolean; } declare class NohmoTracker { private config; private state; private queue; private pageStart; private credentialsRejected; /** Listener removers, run by destroy(). */ private teardown; /** * A destroyed tracker must go completely quiet. * * init() is async, so React StrictMode's mount → unmount → mount can destroy * the first tracker while its init is still in flight. When that init finished * it went on to report a page view, which claimed the page-wide duplicate slot * and then had nowhere to go — its queue was already shut. The second, live * tracker's report was then suppressed as a duplicate and the page view was * lost entirely. */ private destroyed; private autoCapture; private errorCapture; private pendingEvents; private initResolve; private readonly initPromise; constructor(config: NohmoConfig); init(): Promise; send(event: string, data?: Record): void; linkUser(userId: string, email?: string, meta?: Record): Promise; trackConversion(slug: string, properties?: Record): void; /** * Report a page view, ignoring an immediate repeat of the same path. * * Two things now report a navigation — the provider's History watcher, and any * usePageView() the app still calls on that screen — and an app that was * written before the watcher existed has those calls everywhere. Without this * guard, upgrading would silently double every page-view number in the * dashboard, which is worse than the missing tracking it replaced: the old * behaviour was visibly absent, this would look plausible and be wrong. * * React 18 StrictMode double-invokes effects in development, which fires the * same duplicate, so this covers that too. * * Scoped as tightly as it can be: the same path, inside half a second. Both * reports of one navigation land in the same tick or the next; a person * genuinely leaving a page and returning to it cannot. */ trackPageView(path?: string): void; trackTimeSpent(path?: string): void; startScrollTracking(): () => void; /** Returns false when the server never took the batch, so the queue can keep it. */ private sendBatch; private generateSessionId; /** * Did the server accept this call? * * identify and linkUser read res.json() straight off the response and never * looked at the status. A rejected API key comes back 401 with a perfectly * parseable body, so the SDK took the miss for a hit: nothing was recorded and * nothing was said. The React Native and Flutter SDKs were fixed for this; the * web one, which is the biggest surface, was not. * * console.error rather than log(), because the person whose integration is * silently dropping everything is the one who has not enabled debug. */ private resOk; private log; getState(): NohmoState; /** Flush queued events immediately. Used by error capture so a crash-class * event isn't lost to the periodic batch if the page is about to unload. */ flushNow(): void; destroy(): void; } interface TrackerSender { send(event: string, data?: Record): void; } declare class AutoCapture { private tracker; private clickCounts; private cleanupFns; private contentChanges; private observer; private deadClickTimers; private lastDeadReport; private stopped; constructor(tracker: TrackerSender); start(): void; stop(): void; private startMutationWatch; /** * Whether this element's job is to do something OUTSIDE the current page — open a mail * client, dial a number, start a download, open a new tab, or submit to another origin. * * All of those leave the page's URL, DOM and network untouched, so the dead-click test * would call them broken every single time. They are working exactly as intended; we * simply have no way to observe the outcome, so we decline to judge them. */ private actsOutsideThePage; private isActionable; /** * Watch what a click actually did. Fires DEAD_CLICK only when nothing at all happened: * no navigation, no DOM content change, no network request. * * Deliberately biased toward silence. Every ambiguous signal is read as "the app * responded", so this under-reports rather than crying wolf — a false dead click sends * an engineer hunting for a bug that does not exist, which is worse than missing one. * The server-side detector in the backend catches what this misses. */ private watchForDeadClick; private page; private captureClick; private captureSubmit; private captureInput; private getRelevantElement; private extractElementProps; private getCleanText; private getSelector; } interface NohmoContextValue { send: (event: string, data?: Record) => void; trackConversion: (slug: string, properties?: Record) => void; trackTimeSpent: (path?: string) => void; /** Report a page view. Goes through the tracker's duplicate guard, so it is * safe to call on a screen the route watcher already reported. */ trackPageView: (path?: string) => void; linkUser: (userId: string, email?: string, meta?: Record) => Promise; } interface NohmoProviderProps { children: React.ReactNode; projectId: string; apiKey: string; options?: Partial; } declare function NohmoProvider({ children, projectId, apiKey, options, }: NohmoProviderProps): react_jsx_runtime.JSX.Element; declare function useNohmo(): NohmoContextValue; /** * Report a page view by hand. * * Rarely needed: the provider already turns every route change into a PAGE_VIEW. * Use it for a screen that changes without touching the URL — a wizard step, a * tab, a modal you treat as a page — or to report a path of your own choosing. * * Safe to leave in place while upgrading: it goes through the same * trackPageView() as the automatic tracking, so calling it on a screen that also * changes the URL is ignored as a duplicate rather than counted twice. */ declare function usePageView(path?: string): void; /** * Next.js entry point. * * Kept as its own export because it is what the Next docs tell people to import, * but there is nothing Next-specific left in it. Route tracking used to live * here, in a child component that watched the History API; it now lives in * NohmoProvider, which means every React app gets it rather than only Next ones. * * Moving it also fixed two things that were invisible from the outside: the * child's first PAGE_VIEW was dropped (its effect ran before the provider had * built the tracker), and route changes went through send() rather than * trackPageView(), so the page clock was never restarted and every TIME_SPENT * after the first navigation measured the whole visit. */ declare function NohmoNextProvider({ children, projectId, apiKey, options, }: { children: React.ReactNode; projectId: string; apiKey: string; options?: Partial; }): react_jsx_runtime.JSX.Element; export { AutoCapture, NohmoConfig, NohmoEvent, NohmoNextProvider, NohmoProvider, NohmoState, NohmoTracker, NohmoUser, useNohmo, useNohmo as useNohmoNext, usePageView };