Skip to content

Instantly share code, notes, and snippets.

@rishubil
Last active October 18, 2026 12:47
Show Gist options
  • Select an option

  • Save rishubil/663e3716533a27247c26ac9e1651bf8e to your computer and use it in GitHub Desktop.

Select an option

Save rishubil/663e3716533a27247c26ac9e1651bf8e to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name Password manager autofill fixes
// @namespace https://gist.github.com/rishubil
// @version 0.3.1
// @description Fixes form fields that password managers such as 1Password cannot fill.
// @match https://arca.live/u/login*
// @downloadURL https://gist.github.com/rishubil/663e3716533a27247c26ac9e1651bf8e/raw/autofill-fixes.user.js
// @updateURL https://gist.github.com/rishubil/663e3716533a27247c26ac9e1651bf8e/raw/autofill-fixes.user.js
// @run-at document-idle
// @grant none
// ==/UserScript==
// To support a new site, add an entry to FIXES and add its pages to @match above.
(function () {
'use strict';
const FIXES = [
{
name: 'arca.live OTP',
matches: (url) => url.hostname === 'arca.live' && url.pathname.startsWith('/u/login'),
apply: fixArcaLiveOtp,
},
];
// The site splits the OTP code into six boxes (#numberInput1-6). Password
// managers do not fill them. This fix adds one plain OTP field and passes a
// complete code to the boxes, where the site's own input handler submits it.
function fixArcaLiveOtp() {
const stage = document.getElementById('stage-6');
const boxes = stage && [...stage.querySelectorAll('.otp-inputs input')];
if (!boxes || boxes.length !== 6) return;
const [firstBox] = boxes;
const field = document.createElement('input');
field.type = 'text';
field.id = 'otp';
field.name = 'otp';
field.autocomplete = 'one-time-code';
field.inputMode = 'numeric';
field.maxLength = 7;
field.placeholder = 'OTP';
field.setAttribute('aria-label', 'OTP code');
field.style.cssText = 'display:block;width:100%;box-sizing:border-box;margin:0 0 12px;'
+ 'padding:8px;font-size:1.2em;text-align:center;letter-spacing:.3em;';
stage.querySelector('.otp-inputs').before(field);
// Only the new field may claim the one-time code.
firstBox.setAttribute('autocomplete', 'off');
// In the OTP stage, <body> takes digit keys and paste away from every input.
for (const type of ['keydown', 'paste']) {
field.addEventListener(type, (event) => event.stopPropagation());
}
const submit = (code) => {
for (const box of boxes) box.value = '';
firstBox.value = code;
firstBox.dispatchEvent(new InputEvent('input'));
};
// A password manager can fill the code together with the username and the
// password, before the OTP stage opens. The site would submit that code
// too early, so hold it until the OTP stage opens.
let pendingCode = '';
// Pass only a complete code. On a partial code, the site moves focus to the next box.
field.addEventListener('input', () => {
const code = field.value.replace(/\D/g, '');
if (code.length !== 6) return;
if (stage.classList.contains('active')) {
submit(code);
} else {
pendingCode = code;
}
});
// The site calls focus() on box 1 when it opens or resets the OTP stage.
// A focus listener is not enough: the browser fires no focus event while
// the window is in the background.
firstBox.focus = () => {
if (pendingCode) {
submit(pendingCode);
pendingCode = '';
return;
}
field.value = '';
field.focus();
};
}
const url = new URL(location.href);
for (const fix of FIXES) {
if (fix.matches(url)) fix.apply();
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment