@slopit/behavioral API Reference
The behavioral package provides DOM-based behavioral capture including keystroke dynamics, focus tracking, paste detection, and tools for detection and intervention.
Installation
pnpm add @slopit/behavioral
This package depends on @slopit/core, which is installed automatically.
BehavioralCapture
The main class for coordinating behavioral data capture.
Constructor
class BehavioralCapture {
constructor(config: BehavioralCaptureConfig);
}
Factory Function
function createBehavioralCapture(config: BehavioralCaptureConfig): BehavioralCapture;
Example:
import { createBehavioralCapture } from "@slopit/behavioral";
const capture = createBehavioralCapture({
keystroke: { enabled: true },
focus: { enabled: true },
paste: { enabled: true, prevent: false },
});
Methods
attach()
Attach capture to a DOM element.
attach(element: HTMLElement | Document, startTime?: number): void;
- element: The element to attach to. For text input capture, attach to the input or textarea. For document-level focus tracking, attach to document.
- startTime: Optional trial start time (defaults to
performance.now())
Example:
const textarea = document.getElementById("response") as HTMLTextAreaElement;
capture.attach(textarea);
detach()
Detach capture and clean up event listeners.
detach(): void;
Always call detach() when capture is no longer needed to prevent memory leaks.
reset()
Reset all collected data without detaching.
reset(newStartTime?: number): void;
Useful when reusing the same capture instance for multiple trials.
getData()
Get all collected behavioral data.
getData(): BehavioralData;
Returns an object containing:
keystrokes: Array of keystroke eventsfocus: Array of focus eventspaste: Array of paste eventsmetrics: Computed metrics from the data
Example:
const data = capture.getData();
console.log("Keystrokes:", data.keystrokes?.length);
console.log("Mean IKI:", data.metrics?.keystroke?.meanIKI);
getMetrics()
Get computed metrics from collected data.
getMetrics(): BehavioralMetrics;
Example:
const metrics = capture.getMetrics();
if (metrics.keystroke) {
console.log("Total keystrokes:", metrics.keystroke.totalKeystrokes);
console.log("Deletions:", metrics.keystroke.deletions);
}
getFlags()
Get flags generated during capture.
getFlags(): CaptureFlag[];
Flags indicate potential issues like paste events or excessive window switching.
Example:
const flags = capture.getFlags();
for (const flag of flags) {
console.log(`${flag.severity}: ${flag.message}`);
}
on() / off()
Subscribe to and unsubscribe from capture events.
on<K extends keyof BehavioralCaptureEvents>(
event: K,
handler: BehavioralCaptureEvents[K]
): void;
off<K extends keyof BehavioralCaptureEvents>(
event: K,
handler: BehavioralCaptureEvents[K]
): void;
Example:
const handleKeystroke = (event: KeystrokeEvent) => {
console.log("Key pressed:", event.key);
};
capture.on("keystroke", handleKeystroke);
// Later...
capture.off("keystroke", handleKeystroke);
Configuration
BehavioralCaptureConfig
interface BehavioralCaptureConfig {
keystroke?: KeystrokeCaptureConfig;
focus?: FocusCaptureConfig;
paste?: PasteCaptureConfig;
clipboard?: ClipboardCaptureConfig;
}
KeystrokeCaptureConfig
interface KeystrokeCaptureConfig {
enabled?: boolean; // Default: true
captureKeyUp?: boolean; // Default: true
includeModifiers?: boolean; // Default: true
}
- enabled: Whether to capture keystrokes
- captureKeyUp: Whether to capture keyup events in addition to keydown
- includeModifiers: Whether to include modifier key states (Shift, Ctrl, Alt, Meta)
FocusCaptureConfig
interface FocusCaptureConfig {
enabled?: boolean; // Default: true
useVisibilityAPI?: boolean; // Default: true
useBlurFocus?: boolean; // Default: true
}
- enabled: Whether to capture focus events
- useVisibilityAPI: Whether to use the Page Visibility API
- useBlurFocus: Whether to use window blur/focus events
PasteCaptureConfig
interface PasteCaptureConfig {
enabled?: boolean; // Default: true
prevent?: boolean; // Default: false
warnMessage?: string; // Message shown when paste is prevented
capturePreview?: boolean; // Default: true
previewLength?: number; // Default: 100
}
- enabled: Whether to capture paste events
- prevent: Whether to prevent paste operations from completing
- warnMessage: Alert message shown when paste is prevented
- capturePreview: Whether to capture a preview of pasted text
- previewLength: Length of text preview to capture
ClipboardCaptureConfig
interface ClipboardCaptureConfig {
enabled?: boolean; // Default: false
}
- enabled: Whether to capture clipboard copy events
InputWrapper
A higher-level wrapper that combines behavioral capture with detection and intervention. Use InputWrapper when you need more than basic capture, for example when you want to run periodic detectors or trigger interventions based on behavioral patterns.
Factory Function
function createInputWrapper(config?: InputWrapperConfig): InputWrapper;
InputWrapperConfig
interface InputWrapperConfig {
capture?: BehavioralCaptureConfig;
interventionManager?: InterventionManager;
detectors?: Detector[];
detectionInterval?: number; // Default: 5000ms
}
Methods
wrap()
Wrap an input element with behavioral capture.
wrap(element: HTMLTextAreaElement | HTMLInputElement): void;
Example:
import { createInputWrapper } from "@slopit/behavioral";
const wrapper = createInputWrapper({
capture: { keystroke: { enabled: true } },
detectionInterval: 3000,
});
const textarea = document.getElementById("response") as HTMLTextAreaElement;
wrapper.wrap(textarea);
unwrap()
Unwrap the element and clean up all resources.
unwrap(): void;
reset()
Reset all collected data without unwrapping.
reset(newStartTime?: number): void;
getData()
Get all captured data including flags, detection results, and interventions.
getData(): EnhancedBehavioralData;
Returns an EnhancedBehavioralData object:
interface EnhancedBehavioralData extends BehavioralData {
flags: CaptureFlag[];
detectionResults: DetectionResult[];
interventions: InterventionResult[];
}
getFlags()
Get flags generated during capture.
getFlags(): CaptureFlag[];
getDetectionResults()
Get all detection results from detector runs.
getDetectionResults(): DetectionResult[];
getInterventionResults()
Get all intervention results.
getInterventionResults(): InterventionResult[];
runDetection()
Manually trigger all detectors immediately.
runDetection(): DetectionResult[];
on() / off()
Subscribe to wrapper events.
on(event: WrapperEvent, handler: WrapperEventHandler<unknown>): void;
off(event: WrapperEvent, handler: WrapperEventHandler<unknown>): void;
Event types: "data", "flag", "intervention", "detection"
Example:
wrapper.on("detection", (result) => {
if (result.detected) {
console.log("Detected:", result.message);
}
});
Detection
Detection modules identify specific behaviors during capture.
Detector Interface
interface Detector {
readonly name: string;
detect(): DetectionResult;
startMonitoring?(element: HTMLElement): void;
stopMonitoring?(): void;
}
interface DetectionResult {
detectorName: string;
detected: boolean;
confidence: number; // 0.0 to 1.0
message: string;
indicators: DetectionIndicator[];
timestamp: number;
}
interface DetectionIndicator {
name: string;
value: unknown;
weight: number;
}
TextAppearanceDetector
Detects text that appears without corresponding keystroke activity (e.g., from AI assistants or text expansion).
import { TextAppearanceDetector } from "@slopit/behavioral";
const detector = new TextAppearanceDetector({
element: textarea,
thresholds: {
minTextIncrease: 20, // Minimum character increase to flag
maxKeystrokesForIncrease: 5, // Maximum keystrokes for that increase
windowMs: 1000, // Time window for measurement
},
callbacks: {
onTextAppearance: (info) => {
console.log("Suspicious text appearance:", info);
},
},
});
detector.startMonitoring(textarea);
ExtensionDetector
Detects browser extensions that may affect input behavior.
import { createExtensionDetector } from "@slopit/behavioral";
const detector = createExtensionDetector({
checkGrammarly: true,
checkGoogleDocs: true,
customSelectors: [".my-extension-class"],
});
const result = detector.detect();
if (result.detected) {
console.log("Extensions detected:", result.indicators);
}
Intervention
Intervention modules apply behavioral interventions and challenges.
InterventionManager
Evaluates behavioral state and triggers interventions based on configurable thresholds.
import { createInterventionManager } from "@slopit/behavioral";
const manager = createInterventionManager({
triggers: {
paste: {
enabled: true,
maxPasteLength: 50,
maxPasteCount: 2,
},
blur: {
enabled: true,
maxBlurCount: 5,
maxBlurDuration: 30000,
},
idle: {
enabled: true,
idleThresholdMs: 60000,
},
},
interventions: {
warning: { enabled: true },
challenge: { enabled: true, type: "typing-test" },
},
});
InterventionManagerConfig
interface InterventionManagerConfig {
triggers: TriggerConfig;
interventions: {
warning?: { enabled: boolean; message?: string };
challenge?: { enabled: boolean; type: ChallengeType };
block?: { enabled: boolean };
};
}
interface TriggerConfig {
paste?: PasteThreshold;
blur?: BlurThreshold;
idle?: IdleThreshold;
timing?: TimingThreshold;
}
Challenges
Interactive challenges for participant verification.
TypingTestChallenge
Presents a phrase for the participant to type.
import { TypingTestChallenge } from "@slopit/behavioral";
const challenge = new TypingTestChallenge({
phrase: "The quick brown fox jumps over the lazy dog.",
container: document.getElementById("challenge-container"),
timeout: 60000,
});
const result = await challenge.present();
console.log("Passed:", result.passed);
console.log("Accuracy:", result.accuracy);
MemoryRecallChallenge
Presents a series of items for the participant to recall.
import { MemoryRecallChallenge } from "@slopit/behavioral";
const challenge = new MemoryRecallChallenge({
items: ["apple", "banana", "cherry"],
displayTimeMs: 3000,
container: document.getElementById("challenge-container"),
});
const result = await challenge.present();
console.log("Recalled:", result.recalledItems);
Factory Function
import { createChallenge } from "@slopit/behavioral";
const challenge = createChallenge("typing-test", {
phrase: "Type this phrase.",
container: element,
});
Individual Collectors
For advanced use cases, you can use collectors directly.
KeystrokeCollector
Collects keystroke events from DOM elements.
class KeystrokeCollector {
constructor(
config: Required<KeystrokeCaptureConfig>,
startTime: number,
onEvent?: (event: KeystrokeEvent) => void
);
attach(element: HTMLElement | Document): void;
detach(): void;
reset(newStartTime?: number): void;
getEvents(): KeystrokeEvent[];
getFirstKeystrokeTime(): number | undefined;
getRecentCount(windowMs: number): number;
}
Example:
import { KeystrokeCollector } from "@slopit/behavioral";
const collector = new KeystrokeCollector(
{ enabled: true, captureKeyUp: true, includeModifiers: true },
performance.now(),
(event) => console.log("Keystroke:", event.key)
);
collector.attach(document.getElementById("input")!);
FocusCollector
Collects focus and visibility events.
class FocusCollector {
constructor(
config: Required<FocusCaptureConfig>,
startTime: number,
onEvent?: (event: FocusEvent) => void
);
attach(element: Document): void;
detach(): void;
reset(newStartTime?: number): void;
getEvents(): FocusEvent[];
}
PasteCollector
Collects paste events from DOM elements.
class PasteCollector {
constructor(
config: Required<Omit<PasteCaptureConfig, "warnMessage">> & { warnMessage?: string },
startTime: number,
getRecentKeystrokes: () => number,
onEvent?: (event: PasteEvent) => void
);
attach(element: HTMLElement | Document): void;
detach(): void;
reset(newStartTime?: number): void;
getEvents(): PasteEvent[];
}
ClipboardCollector
Collects clipboard copy events.
class ClipboardCollector {
constructor(
config: { enabled: boolean },
startTime: number,
onEvent?: (event: ClipboardCopyEvent) => void
);
attach(element: HTMLElement | Document): void;
detach(): void;
reset(newStartTime?: number): void;
getEvents(): ClipboardCopyEvent[];
}
Metrics Functions
computeKeystrokeMetrics()
Compute metrics from keystroke events.
function computeKeystrokeMetrics(
keystrokes: KeystrokeEvent[],
pauseThreshold?: number // Default: 2000ms
): KeystrokeMetrics;
Example:
import { computeKeystrokeMetrics } from "@slopit/behavioral";
const metrics = computeKeystrokeMetrics(keystrokes);
console.log("Mean IKI:", metrics.meanIKI);
console.log("Pauses:", metrics.pauseCount);
computeFocusMetrics()
Compute metrics from focus events.
function computeFocusMetrics(focusEvents: FocusEvent[]): FocusMetrics;
Example:
import { computeFocusMetrics } from "@slopit/behavioral";
const metrics = computeFocusMetrics(focusEvents);
console.log("Blur count:", metrics.blurCount);
console.log("Total blur duration:", metrics.totalBlurDuration);
computeTimingMetrics()
Compute timing metrics from keystroke events.
function computeTimingMetrics(
keystrokes: KeystrokeEvent[],
totalResponseTime: number
): TimingMetrics;
Example:
import { computeTimingMetrics } from "@slopit/behavioral";
const metrics = computeTimingMetrics(keystrokes, 30000);
console.log("First keystroke latency:", metrics.firstKeystrokeLatency);
console.log("Characters per minute:", metrics.charactersPerMinute);
Event Types
BehavioralCaptureEvents
interface BehavioralCaptureEvents {
keystroke: (event: KeystrokeEvent) => void;
focus: (event: FocusEvent) => void;
paste: (event: PasteEvent) => void;
}
Use these with the on() and off() methods of BehavioralCapture.