Skip to main content

@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 events
  • focus: Array of focus events
  • paste: Array of paste events
  • metrics: 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.