master
John Lauer rust: Phase 3 complete (vault, cards, wiki auto-sign + view flip, annotate, tab + screen recording, highFps lease, down… 4869577 2mo ago
(() => {
  if (window !== window.top) return;
  if (window.__pupCapV === '${BRIDGE_VERSION}') return;
  window.__pupCapV = '${BRIDGE_VERSION}';
  function vis(el){ return !!el && !el.disabled && el.offsetParent !== null && el.getClientRects().length > 0; }
  // v2.0.123: a password field is not always type=password — Gusto and many SSO/Keycloak themes use
  // type=text with autocomplete="current-password" (or name/id "password") and mask it themselves.
  function isPwd(el){ return el && el.tagName === 'INPUT' && (el.type === 'password' || el.autocomplete === 'current-password' || el.autocomplete === 'new-password' || /(^|[^a-z])password([^a-z]|$)/i.test(el.name || '') || /(^|[^a-z])password([^a-z]|$)/i.test(el.id || '')); }
  function grab(form){
    try {
      var pwd = [].slice.call((form || document).querySelectorAll('input')).filter(vis).filter(isPwd)[0];
      if (!pwd || !pwd.value) return;
      var all = [].slice.call((form || document).querySelectorAll('input'));
      var pi = all.indexOf(pwd);
      var user = [].slice.call((form || document).querySelectorAll('input[autocomplete=username], input[type=email]')).filter(vis).filter(function(e){ return !isPwd(e); })[0];
      if (!user) {
        var cands = [].slice.call((form || document).querySelectorAll('input[type=text], input[type=tel], input:not([type])')).filter(vis).filter(function(e){ return !isPwd(e); });
        user = cands.slice().reverse().filter(function(c){ return all.indexOf(c) < pi; })[0] || cands[0] || null;
      }
      var rec = { host: location.hostname, u: user ? user.value : '', p: pwd.value, ts: Date.now() };
      // Prefer the SYNCHRONOUS binding — it reaches the bridge BEFORE the submit navigates the
      // page away (the global stash below would be wiped by that navigation, missing the capture).
      if (typeof window.__pupCredCapture === 'function') { try { window.__pupCredCapture(JSON.stringify(rec)); } catch (e) {} }
      window.__pupCapturedLogin = rec;   // fallback for SPA logins that never navigate (sweep harvests it)
    } catch (e) {}
  }
  function formHasPwd(f){ try { return !!f && [].slice.call(f.querySelectorAll('input')).some(isPwd); } catch (e) { return false; } }
  addEventListener('submit', function(e){ grab(e.target && e.target.tagName === 'FORM' ? e.target : null); }, true);
  addEventListener('click', function(e){
    try {
      var t = e.target && e.target.closest && e.target.closest('button, input[type=submit], [role=button], a');
      if (!t) return;
      var f = t.form || (t.closest && t.closest('form'));
      // v2.0.128 (consumer bug report #4): was a literal input[type=password] check, so a masked
      // type=text password field (Rails/Keycloak) never matched and capture missed on click. Use the
      // isPwd matcher; if there is no enclosing form, sweep the document (SPA login with no <form>).
      if (formHasPwd(f)) grab(f); else if (!f) grab(null);
    } catch (e2) {}
  }, true);
  // v2.0.121 (John, Gusto miss): ALSO capture the instant the password field loses focus or
  // changes — this fires BEFORE the form submits and, crucially, before a cross-origin SSO redirect
  // (login.gusto.com -> app.gusto.com) tears the page down. Submit-only capture raced that redirect
  // and lost; grabbing on blur/change delivers the creds through the synchronous binding first.
  addEventListener('change', function(e){ try { if (isPwd(e.target) && e.target.value) grab(e.target.form || null); } catch (e2) {} }, true);
  addEventListener('blur', function(e){ try { if (isPwd(e.target) && e.target.value) grab(e.target.form || null); } catch (e2) {} }, true);
  // v2.0.128 (consumer bug report #4): a STANDARD Rails login often submits PROGRAMMATICALLY —
  // form.submit() (and requestSubmit's older cousins) does NOT dispatch the 'submit' event, so
  // submit/click capture silently missed it. Hook the prototype so a programmatic submit still grabs.
  try {
    var _origSubmit = HTMLFormElement.prototype.submit;
    HTMLFormElement.prototype.submit = function(){ try { if (formHasPwd(this)) grab(this); } catch (e) {} return _origSubmit.apply(this, arguments); };
  } catch (e) {}
  // Last-resort net: on Enter inside a login form, and on pagehide before ANY navigation tears the
  // page down, grab whatever password field is currently filled. Cheap, synchronous, dedup is
  // downstream (same host+user+pwd within the session is coalesced).
  addEventListener('keydown', function(e){ try { if (e.key === 'Enter' && isPwd(document.activeElement) && document.activeElement.value) grab(document.activeElement.form || null); } catch (e2) {} }, true);
  addEventListener('pagehide', function(){ try { grab(null); } catch (e2) {} }, true);
})()