Skip to main content

Shared API

:::caution Pre-release This is a pre-1.0 library (v0.1.1) — API may change without notice. :::

Shared types and pure utilities safe to import from both client and server. Import from @next-story/journey-recorder/shared.

Event Types

The four recognized event types:

export type JourneyEventType =
| "page_view"
| "rage_click"
| "dead_click"
| "conversion";

export const JOURNEY_EVENT_TYPES = [
"page_view",
"rage_click",
"dead_click",
"conversion",
] as const;

JourneyPageViewEvent

Emitted when the user navigates to a route.

interface JourneyPageViewEvent {
event_type: "page_view";
route_pattern: string; // Normalized URL without query/hash
ts: number; // Client clock, epoch ms
tab_id: string; // Browser tab identifier
client_seq: number; // Sequence number per session
}

JourneyRageClickEvent

Emitted when the user clicks rapidly (3+ times in 1 second) on the same target.

interface JourneyRageClickEvent {
event_type: "rage_click";
route_pattern: string;
click_target_label: string | null; // Scrubbed or null if excluded
click_count: number; // N of collapsed burst
effect_latency_ms: number | null; // Latency if effect observed
ts: number;
tab_id: string;
client_seq: number;
}

JourneyDeadClickEvent

Emitted when the user clicks but no observable effect occurs within 1 second.

interface JourneyDeadClickEvent {
event_type: "dead_click";
route_pattern: string;
click_target_label: string | null;
effect_latency_ms: null; // Always null for dead clicks
ts: number;
tab_id: string;
client_seq: number;
}

JourneyConversionEvent

Emitted when the user completes a goal, either via explicit track() or conversionRoutePattern.

interface JourneyConversionEvent {
event_type: "conversion";
route_pattern: string;
conversion_source: "route_pattern" | "explicit"; // How it was triggered
conversion_name: string | null; // From track({ name: "..." })
ts: number;
tab_id: string;
client_seq: number;
}

Constants

export const MAX_EVENTS_PER_SESSION = 200; // Hard cap per session
export const SDK_VERSION = "0.0.0"; // Stamped on every batch
export const SDK_RULE_VERSION = 1; // Rule version, independent of SDK version

Click Detection

Pure utilities for classifying clicks. Used by the client; available for custom logic.

classifyClick(input): ClickClassification

Determine if a click is a rage click or dead click.

import { classifyClick } from "@next-story/journey-recorder/shared";

const classification = classifyClick({
elementId: "btn-submit",
clickCount: 3, // N times clicked in succession
effectLatencyMs: null, // Latency if effect observed
});

if (classification === "rage_click") {
console.log("Rage click detected");
} else if (classification === "dead_click") {
console.log("Dead click detected");
} else {
console.log("Normal click");
}

detectRageClick(samples, options?): RageClickResult

Detect a rage click from a sequence of click samples.

import { detectRageClick } from "@next-story/journey-recorder/shared";

const result = detectRageClick(clickSamples, {
thresholds: {
mouse: { minClickCount: 3, windowMs: 1000 },
touch: { minClickCount: 4, windowMs: 1000 },
},
});

if (result.type === "rage_click") {
console.log(`Rage click: ${result.burst.clickCount} clicks`);
}

Route Pattern Normalization

URL normalization for privacy — query strings and hash fragments are always removed.

normalizeRoutePattern(url)

Normalize a URL to a route pattern (removing query and hash).

import { normalizeRoutePattern } from "@next-story/journey-recorder/shared";

normalizeRoutePattern("/checkout?email=test@example.com#section=1");
// Returns: "/checkout"

normalizeRoutePattern("https://example.com/products/123?utm_source=email");
// Returns: "/products/123"

normalizeSegment(segment)

Normalize a single URL segment.

import { normalizeSegment } from "@next-story/journey-recorder/shared";

normalizeSegment("[id]"); // Returns: "[id]" (Next.js dynamic segment)
normalizeSegment("product-123"); // Returns: "product-123"

PII Scrubbing

Pure string utilities for scrubbing sensitive data from click target labels.

scrubLabel(label)

Redact password fields, credit card fields, and other autocomplete-excluded inputs.

import { scrubLabel } from "@next-story/journey-recorder/shared";

scrubLabel("password");
// Returns: "" (empty string for excluded fields)

scrubLabel("First Name");
// Returns: "First Name" (safe label, not redacted)

hasQueryOrHash(url)

Check if a URL contains a query string or hash fragment.

import { hasQueryOrHash } from "@next-story/journey-recorder/shared";

hasQueryOrHash("/checkout");
// Returns: false

hasQueryOrHash("/checkout?email=user@example.com");
// Returns: true

hasQueryOrHash("/checkout#billing");
// Returns: true

stripQueryAndHash(url)

Remove query and hash from a URL.

import { stripQueryAndHash } from "@next-story/journey-recorder/shared";

stripQueryAndHash("https://example.com/checkout?email=test@example.com#section=1");
// Returns: "https://example.com/checkout"

Type Exports

export type JourneyBatch;
export type JourneyClickEvent;
export type JourneyCollectErrorBody;
export type JourneyEvent;
export type JourneyEventDraft;
export type ClickClassification;
export type EffectKind;
export type LabelSource;
export type RageClickResult;
export type RageClickRejection;
// ... and more

See source files in packages/journey-recorder/src/shared/ for complete type definitions.

Validation Helpers

isClickEvent(event): boolean

Check if an event is a click-based event (rage or dead click).

import { isClickEvent } from "@next-story/journey-recorder/shared";

if (isClickEvent(event)) {
console.log("Click target:", event.click_target_label);
}