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);
}