API Reference
Complete reference for all public exports from the WriteTrack SDK.
Import Paths
Section titled “Import Paths”WriteTrack provides multiple entry points via package.json exports:
| Import Path | Format | Description |
|---|---|---|
writetrack | ESM, CJS, Browser | Core SDK — WriteTrack class and types |
writetrack/pipes | ESM, CJS, Browser | Output sink factories (also re-exported from writetrack) |
writetrack/browser | ESM | Browser bundle (same as core, explicit browser entry) |
Frameworks
Section titled “Frameworks”| Import Path | Format | Description |
|---|---|---|
writetrack/react | ESM | React hook — useWriteTrack |
writetrack/vue | ESM | Vue composable — useWriteTrack |
Rich-Text Editors
Section titled “Rich-Text Editors”| Import Path | Format | Description |
|---|---|---|
writetrack/tiptap | ESM | TipTap extension — WriteTrackExtension |
writetrack/ckeditor | ESM | CKEditor 5 plugin — WriteTrackPlugin |
writetrack/prosemirror | ESM | ProseMirror plugin — WriteTrackPlugin |
writetrack/quill | ESM | Quill module — WriteTrackModule |
writetrack/lexical | ESM | Lexical integration — createWriteTrackLexical |
writetrack/slate | ESM | Slate integration — createWriteTrackSlate |
writetrack/tinymce | ESM | TinyMCE integration — createWriteTrackTinyMCE |
Utilities
Section titled “Utilities”| Import Path | Format | Description |
|---|---|---|
writetrack/verify | ESM | Server-side signature verification |
writetrack/viz | ESM | Session visualization components |
ESM (recommended)
Section titled “ESM (recommended)”import { WriteTrack, webhook, datadog, segment, opentelemetry, analyzeEvents, formatIndicator,} from 'writetrack';import { useWriteTrack } from 'writetrack/react';import { useWriteTrack } from 'writetrack/vue';import { WriteTrackExtension } from 'writetrack/tiptap';import { WriteTrackPlugin } from 'writetrack/ckeditor';import { WriteTrackPlugin } from 'writetrack/prosemirror';import { WriteTrackModule } from 'writetrack/quill';import { createWriteTrackLexical } from 'writetrack/lexical';import { createWriteTrackSlate } from 'writetrack/slate';import { createWriteTrackTinyMCE } from 'writetrack/tinymce';import { verifyAnalysisSignatureAsync } from 'writetrack/verify';import { WtScorecard } from 'writetrack/viz';CommonJS
Section titled “CommonJS”const { WriteTrack, webhook } = require('writetrack');Script Tag
Section titled “Script Tag”<script type="module"> import { WriteTrack } from 'https://unpkg.com/writetrack/dist/browser/index.js';</script>All types are exported from their respective entry points:
import type { WriteTrackOptions, WriteTrackDataSchema, SessionAnalysis, SessionReport, IndicatorOutput, KeystrokeEvent, ClipboardEvent, SelectionEvent, UndoRedoEvent, ProgrammaticInsertionEvent, CompositionEvent, InputSource, WriteTrackSink, WebhookOptions, DatadogOptions, SegmentOptions, OpenTelemetryOptions,} from 'writetrack';WriteTrack Class
Section titled “WriteTrack Class”The main class for keystroke tracking and behavioral analysis.
Constructor
Section titled “Constructor”new WriteTrack(optionsOrTarget: WriteTrackOptions | HTMLElement)Creates a new WriteTrack instance. Accepts either a full options object or an HTMLElement directly for localhost evaluation.
| Parameter | Type | Description |
|---|---|---|
optionsOrTarget | WriteTrackOptions | HTMLElement | Configuration options or target element |
// Full options with contextconst tracker = new WriteTrack({ target: document.querySelector('#response-field'), license: 'wt_live_...', userId: 'u_abc123', contentId: 'post_draft_42', metadata: { formName: 'signup' },});
// Shorthand (localhost evaluation)const tracker = new WriteTrack(document.querySelector('#response-field'));start()
Section titled “start()”start(): voidBegins recording keystroke events. Clears any previously captured data.
- Resets all event arrays (keystrokes, clipboard, selection, undo/redo, programmatic insertion, composition)
- Records session start time
- Enables event capture
const tracker = new WriteTrack({ target: textarea });tracker.start(); // Now recording
// User types...
const events = tracker.getRawEvents();stop()
Section titled “stop()”stop(): voidStops recording keystrokes.
- Disables event capture
- Cleans up selection polling interval and mutation observer
- Data remains available after stopping
tracker.stop();// Data still accessibleconst events = tracker.getRawEvents();getData()
Section titled “getData()”getData(): WriteTrackDataSchemaReturns the complete session as a structured WriteTrackDataSchema object, including metadata, all captured events, and computed quality metrics.
tracker.stop();const data = tracker.getData();// {// version: "2.1.0",// metadata: { tool, targetElement, timestamp, duration },// session: { events, clipboardEvents, selectionEvents, compositionEvents, ... },// quality: { overallScore, sequenceValid, qualityLevel, ... },// }
// Send to your serverawait fetch('/api/submit', { method: 'POST', body: JSON.stringify(data),});getText()
Section titled “getText()”getText(): stringReturns the current text content of the target element. Reads value for input/textarea elements, or textContent for other elements.
getRawEvents()
Section titled “getRawEvents()”getRawEvents(): KeystrokeEvent[]Returns a copy of all captured keystroke events.
const events = tracker.getRawEvents();console.log(`Captured ${events.length} keystrokes`);
events .filter((e) => e.type === 'keydown') .forEach((e) => { console.log(`${e.key} at ${e.timestamp}ms, flight: ${e.flightTime}ms`); });getClipboardEvents()
Section titled “getClipboardEvents()”getClipboardEvents(): ClipboardEvent[]Returns a copy of all clipboard events (copy, paste, cut).
const pastes = tracker.getClipboardEvents().filter((e) => e.type === 'paste');console.log(`User pasted ${pastes.length} times`);
pastes.forEach((e) => { console.log(`Pasted ${e.length} chars at position ${e.position}`);});getSelectionEvents()
Section titled “getSelectionEvents()”getSelectionEvents(): SelectionEvent[]Returns text selection events.
const selections = tracker.getSelectionEvents();selections.forEach((e) => { console.log(`Selected ${e.selectedLength} chars via ${e.method}`);});getUndoRedoEvents()
Section titled “getUndoRedoEvents()”getUndoRedoEvents(): UndoRedoEvent[]Returns undo/redo operation events.
getProgrammaticInsertionEvents()
Section titled “getProgrammaticInsertionEvents()”getProgrammaticInsertionEvents(): ProgrammaticInsertionEvent[]Returns detected programmatic insertion events. These are emitted when text appears in the input without corresponding keystrokes, indicating browser autocomplete, autofill, predictive text, AI extension injection, voice dictation, or programmatic value assignment.
const insertions = tracker.getProgrammaticInsertionEvents();insertions.forEach((e) => { console.log( `Programmatic insertion: "${e.insertedText}" (${e.insertedLength} chars)` );});getCompositionEvents()
Section titled “getCompositionEvents()”getCompositionEvents(): CompositionEvent[]Returns IME composition events captured during the session. A composition event is emitted when a CJK (Chinese, Japanese, Korean) input method completes a composition sequence. Intermediate keystrokes during composition are suppressed.
const compositions = tracker.getCompositionEvents();compositions.forEach((e) => { console.log(`Composition: ${e.insertedLength} chars in ${e.duration}ms`);});getSessionDuration()
Section titled “getSessionDuration()”getSessionDuration(): numberReturns the total session duration in milliseconds since start() was called, including time when the tab was hidden.
const duration = tracker.getSessionDuration();console.log(`Session: ${(duration / 1000).toFixed(1)} seconds`);getActiveTime()
Section titled “getActiveTime()”getActiveTime(): numberReturns the active (visible) session time in milliseconds. This subtracts time spent with the tab hidden from the total session duration. Returns 0 before start() is called.
const active = tracker.getActiveTime();const total = tracker.getSessionDuration();console.log( `Active: ${(active / 1000).toFixed(1)}s of ${(total / 1000).toFixed(1)}s total`);getKeystrokeCount()
Section titled “getKeystrokeCount()”getKeystrokeCount(): numberReturns the total number of keydown events captured.
pipe()
Section titled “pipe()”pipe(sink: WriteTrackSink): thisRegisters an output sink to receive session data when getData() is called. Returns this for chaining.
import { WriteTrack, webhook } from 'writetrack';
tracker .pipe(webhook({ url: 'https://api.example.com/typing' })) .pipe({ send: async (data) => console.log(data) });See Output Sinks for full documentation of the pipe system and available sinks.
on(event: string, handler: (...args: unknown[]) => void): thisRegisters an event listener.
Supported events:
| Event | Handler signature | Description |
|---|---|---|
pipe:error | (err: Error, sink: WriteTrackSink) => void | Fires when a sink fails |
tick | (data: { activeTime: number; totalTime: number }) => void | Fires every ~1 second while the session is active and the tab is visible |
tracker.on('pipe:error', (err, sink) => { console.error('Sink failed:', err);});
// Display a live session timertracker.on('tick', ({ activeTime, totalTime }) => { const seconds = Math.floor(activeTime / 1000); console.log(`Active: ${seconds}s`);});isLicenseValid()
Section titled “isLicenseValid()”isLicenseValid(): booleanReturns whether a valid, non-expired license key has been verified for the current domain. Returns false when no license key is provided, during the grace period, or when validation fails. Recording (event capture) works regardless of license status — only getAnalysis() requires a valid license, grace period, or localhost.
isLicenseValidated()
Section titled “isLicenseValidated()”isLicenseValidated(): booleanReturns whether license validation has been performed.
if (tracker.isLicenseValidated() && tracker.isLicenseValid()) { console.log('License valid, ready to use');}isTargetDetached()
Section titled “isTargetDetached()”isTargetDetached(): booleanReturns whether the monitored target element has been removed from the DOM. Useful for detecting SPA navigation that destroys the tracked element.
if (tracker.isTargetDetached()) { console.warn('Target element removed — call stop() and create a new tracker');}ready: Promise<void>;A promise that resolves when the tracker is ready for use. When persist: true, this resolves after IndexedDB loads any prior session data. When persist: false, it resolves immediately. After stop() with persistence enabled, ready resolves when the save completes.
const tracker = new WriteTrack({ target: textarea, contentId: 'doc-1', persist: true,});await tracker.ready; // IndexedDB loadedtracker.start(); // Will auto-resume prior session if availableclearPersistedData()
Section titled “clearPersistedData()”async clearPersistedData(): Promise<void>Removes any persisted session data from IndexedDB for this field. No-op when persist is not enabled. Use this to reset a field’s session history.
await tracker.clearPersistedData();getAnalysis()
Section titled “getAnalysis()”async getAnalysis(): Promise<SessionAnalysis | null>Runs WASM-powered analysis on the captured session data. The WASM binary is lazy-loaded on the first call and cached for subsequent calls. Returns null if the WASM module fails to load.
const tracker = new WriteTrack({ target: textarea, wasmUrl: '/writetrack.wasm',});tracker.start();
// ... user types ...
tracker.stop();const analysis = await tracker.getAnalysis();// {// version: "0.9.1",// sufficientData: true,// contentOrigin: { ... },// timingAuthenticity: { ... },// ...// }getSessionReport()
Section titled “getSessionReport()”async getSessionReport(): Promise<SessionReport>Returns both the raw capture data and the WASM analysis in a single call.
const report = await tracker.getSessionReport();// {// data: WriteTrackDataSchema, // Same as getData()// analysis: SessionAnalysis, // Same as getAnalysis()// }
await fetch('/api/submit', { method: 'POST', body: JSON.stringify(report),});wasmReady
Section titled “wasmReady”get wasmReady(): booleanWhether the WASM module has been loaded and is ready for analysis. Returns false until getAnalysis() is called for the first time and the WASM binary loads successfully.
const tracker = new WriteTrack({ target: textarea });console.log(tracker.wasmReady); // false
await tracker.getAnalysis();console.log(tracker.wasmReady); // true (if WASM loaded)Static: listPersistedSessions()
Section titled “Static: listPersistedSessions()”static async listPersistedSessions(): Promise<PersistedSessionInfo[]>Returns metadata for all persisted sessions stored in IndexedDB. Useful for multi-document apps that need to display or manage saved writing sessions.
const sessions = await WriteTrack.listPersistedSessions();// [{ contentId: 'doc_1', fieldId: 'default', keystrokeCount: 42, lastSavedAt: 1709..., sessionId: '...' }]Static: deletePersistedSession()
Section titled “Static: deletePersistedSession()”static async deletePersistedSession(contentId: string, fieldId?: string): Promise<void>Deletes persisted session data from IndexedDB. If fieldId is omitted, deletes all sessions for the given contentId.
// Delete all sessions for a documentawait WriteTrack.deletePersistedSession('doc_1');
// Delete a specific field's sessionawait WriteTrack.deletePersistedSession('doc_1', 'response');Pipes Exports
Section titled “Pipes Exports”import { webhook, datadog, segment, opentelemetry } from 'writetrack';| Export | Returns | Description |
|---|---|---|
webhook(options) | WriteTrackSink | POST session data to a URL |
datadog(options) | WriteTrackSink | Send as Datadog RUM custom action |
segment(options) | WriteTrackSink | Send as Segment track event |
opentelemetry(options) | WriteTrackSink | Send as OpenTelemetry span |
See Output Sinks for configuration options and usage guides.
Standalone Exports
Section titled “Standalone Exports”analyzeEvents()
Section titled “analyzeEvents()”import { analyzeEvents } from 'writetrack';
async function analyzeEvents( rawEventData: unknown, options?: { wasmUrl?: string; licenseKey?: string }): Promise<SessionAnalysis | null>;Analyze a WriteTrackDataSchema object without needing a live DOM element. Useful for server-side analysis of previously captured session data.
// Server-side: analyze data received from the clientconst data = await request.json(); // WriteTrackDataSchema from getData()const analysis = await analyzeEvents(data, { licenseKey: process.env.WRITETRACK_LICENSE_KEY,});formatIndicator()
Section titled “formatIndicator()”import { formatIndicator } from 'writetrack';
function formatIndicator(indicator: IndicatorOutput): string;Converts a machine-readable WT-NNN indicator code (from SessionAnalysis) into a human-readable English string.
const analysis = await tracker.getAnalysis();if (analysis) { const msg = formatIndicator(analysis.contentOrigin.indicator); // e.g., "85% of text was pasted from external sources"}createSessionReport()
Section titled “createSessionReport()”import { createSessionReport } from 'writetrack';
async function createSessionReport( rawData: unknown, options?: { wasmUrl?: string; licenseKey?: string }): Promise<SessionReport>;Analyze raw session data and return a complete SessionReport (data + analysis). Wraps analyzeEvents() — accepts WriteTrackDataSchema (from getData()) or raw typing-sample format (with rawEvents instead of session). The returned report is ready for scorecard.setData().
const data = await request.json();const report = await createSessionReport(data, { licenseKey: process.env.WRITETRACK_LICENSE_KEY,});// report.data — normalized input data// report.analysis — SessionAnalysis resultWriteTrackOptions
Section titled “WriteTrackOptions”Configuration options for the WriteTrack constructor.
interface WriteTrackOptions { target: HTMLElement; // Required: Input element to monitor license?: string; // Optional: License key for production userId?: string; // Optional: User identifier (included in metadata) contentId?: string; // Optional: Content/document identifier (included in metadata) metadata?: Record<string, unknown>; // Optional: Arbitrary tags (appears as metadata.custom) wasmUrl?: string; // Optional: URL to WASM binary for analysis persist?: boolean; // Optional: Enable IndexedDB session persistence (requires contentId) cursorPositionProvider?: () => number; // Optional: Custom cursor position for rich-text editors inputSourceProvider?: () => InputSource | undefined; // Optional: Callback returning current input source classification. Set automatically by editor integrations.}PersistedSessionInfo
Section titled “PersistedSessionInfo”Metadata about a persisted writing session, returned by listPersistedSessions().
interface PersistedSessionInfo { contentId: string; // Document/content identifier fieldId: string; // Field identifier within the document keystrokeCount: number; // Number of keystrokes in the persisted session lastSavedAt: number; // Unix timestamp of last save sessionId: string; // Unique session ID}WriteTrackDataSchema
Section titled “WriteTrackDataSchema”The complete session output returned by getData().
interface WriteTrackDataSchema { version: '2.1.0'; metadata: SessionMetadata; session: RawEventData; quality: DataQualityMetrics; unlicensed?: boolean;}SessionMetadata
Section titled “SessionMetadata”Session metadata included in the export.
interface SessionMetadata { tool: { name: string; version: string }; targetElement: string; // e.g., 'textarea', 'input' fieldId: string; // Derived from data-writetrack-field, id, or name attribute sessionId: string; // Unique session ID (stable across resume) segment?: number; // Resume segment counter (present when session was resumed) timestamp: string; // ISO 8601 duration: number; // ms userId?: string; // From WriteTrackOptions.userId contentId?: string; // From WriteTrackOptions.contentId custom?: Record<string, unknown>; // From WriteTrackOptions.metadata}DataQualityMetrics
Section titled “DataQualityMetrics”Quality assessment of the captured session.
interface DataQualityMetrics { overallScore: number; // 0-1 completeness: number; // 0-1 sequenceValid: boolean; // Timestamps monotonically increasing timingValid: boolean; // Session has valid timing data sessionDuration: number; // ms eventCount: number; qualityLevel: 'POOR' | 'FAIR' | 'GOOD' | 'EXCELLENT'; issues: string[]; // Human-readable quality issues validatedAt: string; // ISO 8601}KeystrokeEvent
Section titled “KeystrokeEvent”Individual keystroke event data.
interface KeystrokeEvent { key: string; // Character or key name code: string; // Physical key code type: 'keydown' | 'keyup'; timestamp: number; // High-precision timestamp dwellTime?: number; // Key hold duration (keyup only) flightTime?: number; // Time since last key (keydown only) isCorrection?: boolean; // Backspace/Delete/ArrowLeft/ArrowRight windowFocused: boolean; timeSinceLastMouse: number; isTrusted?: boolean; // true if user-generated, false if scripted inputSource?: InputSource; // Input source classification, set by editor integrations or beforeinput fallback isBurst?: boolean; // Schema field, populated by analysis pipelines cursorPosition?: number; // Schema field, populated by analysis pipelines modifiers?: ModifierState; // Schema field, populated by analysis pipelines}ClipboardEvent
Section titled “ClipboardEvent”Clipboard operation event data.
interface ClipboardEvent { type: 'copy' | 'paste' | 'cut'; timestamp: number; position: number; length: number; shortcut: string; // e.g., 'Ctrl+V', 'Cmd+V' content?: string; // Clipboard content (paste only) beforeText?: string; // Text state before operation afterText?: string; // Text state after operation replacedText?: string; // Text that was replaced replacedRange?: { start: number; end: number; }; isTrusted?: boolean; // true if user-generated, false if scripted inputSource?: InputSource; // Input source classification, set by editor integrations or beforeinput fallback}SelectionEvent
Section titled “SelectionEvent”Text selection event data.
interface SelectionEvent { type: 'select'; timestamp: number; startPosition: number; endPosition: number; selectedLength: number; selectedText?: string; // The selected text content (omitted when privacy-sensitive) method: 'mouse' | 'keyboard' | 'programmatic'; isTrusted?: boolean; // true if user-generated, false if scripted}ProgrammaticInsertionEvent
Section titled “ProgrammaticInsertionEvent”Programmatic insertion event data.
interface ProgrammaticInsertionEvent { type: 'programmatic-insertion'; timestamp: number; insertedText: string; // Text that was inserted insertedLength: number; // Character count position: number; // Cursor position where text was inserted isTrusted: boolean; // Whether the triggering input event was user-generated inputType?: string; // InputEvent.inputType if available inputSource?: InputSource; // Input source classification, set by editor integrations or beforeinput fallback}UndoRedoEvent
Section titled “UndoRedoEvent”Undo/redo operation event data.
interface UndoRedoEvent { type: 'undo' | 'redo'; timestamp: number; shortcut: string; // e.g., 'Ctrl+Z' beforeText: string; afterText: string;}CompositionEvent
Section titled “CompositionEvent”IME composition event data. Emitted when a CJK input method or dead-key sequence completes.
interface CompositionEvent { timestamp: number; // When the composition started duration: number; // Duration of the composition sequence (ms) insertedLength: number; // Characters inserted (0 if composition was abandoned) isTrusted: boolean; // Whether the compositionend event was user-generated}SessionAnalysis
Section titled “SessionAnalysis”The WASM analysis output returned by getAnalysis(). Contains indicator codes and rich metrics for each analysis dimension.
interface SessionAnalysis { version: string; locale: string; analyzedAt: number; sufficientData: boolean; keydownCount: number; integrity: IntegrityProof; sessionTimeline: SessionSegment[]; initialTextLength: number; initialTextHash: string; finalTextLength: number; finalTextHash: string; contentOrigin: ContentOriginAnalysis; timingAuthenticity: TimingAuthenticityAnalysis; sessionContinuity: SessionContinuityAnalysis; physicalPlausibility: PhysicalPlausibilityAnalysis; revisionBehavior: RevisionBehaviorAnalysis; temporalPatterns: TemporalPatternsAnalysis; writingProcess: WritingProcessAnalysis; outputSignature: string; signedPayload?: string;}Each analysis dimension (e.g., contentOrigin, timingAuthenticity) contains an indicator with a machine-readable code (WT-100 through WT-606) and numeric params, plus a metrics object with detailed data and time series suitable for visualization. Content origin additionally has a pasteReworkIndicator (WT-105 through WT-107) for paste editing behavior.
The outputSignature field contains a cryptographic signature over the analysis output, verifiable via verifyAnalysisSignatureAsync() from writetrack/verify. The optional signedPayload field contains the exact JSON bytes that were signed, for cross-language verification.
WritingProcessAnalysis
Section titled “WritingProcessAnalysis”Writing process stage classification returned in SessionAnalysis.writingProcess.
interface WritingProcessAnalysis { indicator: IndicatorOutput; metrics: { segments: WritingProcessSegment[]; transitions: PhaseTransition[]; timeInPhase: { planning: number; // Fraction of session time (0-1) drafting: number; revision: number; }; phaseChangeCount: number; windowDurationMs: number; phaseTimeline: TimeSeries; };}
interface WritingProcessSegment { startTime: number; // ms from session start endTime: number; phase: 'planning' | 'drafting' | 'revision'; confidence: number; // 0-1 features: WindowFeatures;}
interface PhaseTransition { from: 'planning' | 'drafting' | 'revision'; to: 'planning' | 'drafting' | 'revision'; count: number; probability: number; // 0-1}SessionReport
Section titled “SessionReport”Combined output of getSessionReport().
interface SessionReport { data: WriteTrackDataSchema; analysis: SessionAnalysis | null;}IndicatorOutput
Section titled “IndicatorOutput”Machine-readable indicator returned by each analysis dimension.
interface IndicatorOutput { code: string; // WT-NNN code (e.g., "WT-101") params: Record<string, number>; // Numeric parameters (e.g., { pct: 85 })}Use formatIndicator() to convert to a human-readable English string.