Skip to content

Session Persistence

By default, session data lives only in memory and is lost on page reload. Enable persistence to automatically save and restore sessions via IndexedDB.

Pass persist: true with a contentId to the constructor:

const tracker = new WriteTrack({
target: textarea,
contentId: 'post_draft_42', // Required with persist
persist: true,
});
// Wait for any prior session data to load from IndexedDB
await tracker.ready;
// start() will auto-resume the prior session if one exists
tracker.start();

When a session is resumed, all previously captured events are restored and new events are appended. The sessionId stays the same across resumes, and a segment counter in the metadata tracks how many times the session was resumed.

  • Where sessions live: in the browser’s IndexedDB, under the writetrack database and its sessions object store, scoped to the page’s origin. Nothing leaves the device.
  • Storage key: Sessions are stored by contentId:fieldId, where fieldId is derived from data-writetrack-field, id, or name on the target element.
  • Page unload: On beforeunload, the current session is synchronously saved to localStorage as a fallback (IndexedDB transactions don’t survive page unload). On the next load, this is promoted back to IndexedDB.
  • ready promise: Resolves when IndexedDB has loaded any prior session. Always await tracker.ready before calling start() when persistence is enabled.
  • stop(): Saves the session to IndexedDB. After calling stop(), ready resolves again once the save completes.
  • Cleanup: Call clearPersistedData() to remove the stored session for this field (e.g., after successful form submission).

Each field gets its own persisted session, keyed by contentId:fieldId. Give each tracked element a distinct id or data-writetrack-field attribute:

const trackers = ['#title', '#body', '#summary'].map((sel) => {
const el = document.querySelector<HTMLTextAreaElement>(sel)!;
const t = new WriteTrack({ target: el, contentId: 'post_42', persist: true });
return t;
});
await Promise.all(trackers.map((t) => t.ready));
trackers.forEach((t) => t.start());

After the user submits the form, clear persisted data so the next visit starts fresh:

form.addEventListener('submit', async (e) => {
e.preventDefault();
const data = tracker.getData();
tracker.stop();
await tracker.ready; // wait for final save
await tracker.clearPersistedData(); // remove from IndexedDB
await fetch('/api/submit', { method: 'POST', body: JSON.stringify(data) });
});

For multi-document applications, use the static methods to list and delete persisted sessions without creating a WriteTrack instance:

// List all persisted sessions
const sessions = await WriteTrack.listPersistedSessions();
// [{ contentId: 'doc_1', fieldId: 'default', keystrokeCount: 42, ... }]
// Delete all sessions for a document (e.g., when user deletes it)
await WriteTrack.deletePersistedSession('doc_1');
// Delete a specific field's session
await WriteTrack.deletePersistedSession('doc_1', 'body');

Automatic saves are not awaitable. An IndexedDB write becomes durable when its transaction commits. Call flush() before an operation that depends on the completed write:

tracker.stop();
await tracker.flush(); // IndexedDB transaction committed
window.location.href = '/submitted';

flush() is a no-op when persistence is disabled and rejects when the write fails.

The persist:error event reports load, save, flush, and unload-snapshot failures. The unload snapshot uses localStorage and is subject to the browser’s per-origin storage quota.

tracker.on('persist:error', (error, stage) => {
// stage: 'load' | 'save' | 'flush' | 'unload-snapshot'
console.warn(`WriteTrack could not persist (${stage})`, error);
if (stage === 'unload-snapshot') {
// The localStorage unload snapshot was not written.
}
});

An 'unload-snapshot' error means that the localStorage copy was not written. The IndexedDB session remains available unless its own save reports an error.