New GUI tabs probe popular sites for exit-IP bans and prep signup autofill through the chain; Live tab shows geo/ASN/datacenter flags, and sticky-exit keeps the same egress IP during signup flows. Co-authored-by: Cursor <cursoragent@cursor.com>
154 lines
4.3 KiB
JavaScript
154 lines
4.3 KiB
JavaScript
(function () {
|
|
"use strict";
|
|
|
|
var cfg = null;
|
|
var filledKeys = {};
|
|
var startedAt = Date.now();
|
|
var MAX_MS = 120000;
|
|
|
|
function loadConfig(cb) {
|
|
try {
|
|
var rt = typeof browser !== "undefined" ? browser : chrome;
|
|
var url = rt.runtime.getURL("autofill_config.json");
|
|
fetch(url, { cache: "no-store" })
|
|
.then(function (r) { return r.json(); })
|
|
.then(function (j) { cb(j || null); })
|
|
.catch(function () { cb(null); });
|
|
} catch (e) {
|
|
cb(null);
|
|
}
|
|
}
|
|
|
|
function hostMatches(host, patterns) {
|
|
if (!patterns || !patterns.length) return true;
|
|
host = (host || "").toLowerCase();
|
|
for (var i = 0; i < patterns.length; i++) {
|
|
var p = String(patterns[i] || "").toLowerCase();
|
|
if (!p) continue;
|
|
if (p.indexOf("*") >= 0) {
|
|
var re = new RegExp("^" + p.replace(/\./g, "\\.").replace(/\*/g, ".*") + "$");
|
|
if (re.test(host)) return true;
|
|
} else if (host === p || host.endsWith("." + p)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function setValue(el, value) {
|
|
if (!el || value == null || value === "") return false;
|
|
try {
|
|
el.focus();
|
|
el.value = value;
|
|
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
return true;
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function trySelectors(selectors, value) {
|
|
if (!selectors || !value) return false;
|
|
for (var i = 0; i < selectors.length; i++) {
|
|
var nodes = document.querySelectorAll(selectors[i]);
|
|
for (var j = 0; j < nodes.length; j++) {
|
|
var el = nodes[j];
|
|
if (!el || el.disabled || el.readOnly) continue;
|
|
if (el.type === "hidden") continue;
|
|
if (setValue(el, value)) return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function runFill() {
|
|
if (!cfg || !cfg.fields) return;
|
|
if (Date.now() - startedAt > MAX_MS) return;
|
|
var host = location.hostname || "";
|
|
if (cfg.hosts && cfg.hosts.length && !hostMatches(host, cfg.hosts)) return;
|
|
|
|
var fields = cfg.fields;
|
|
var keys = Object.keys(fields);
|
|
for (var i = 0; i < keys.length; i++) {
|
|
var key = keys[i];
|
|
var val = fields[key];
|
|
if (!val) continue;
|
|
if (filledKeys[key]) continue;
|
|
if (trySelectors(fields[key + "_selectors"] || defaultSelectors(key), val)) {
|
|
filledKeys[key] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
function defaultSelectors(key) {
|
|
var map = {
|
|
email: [
|
|
'input[type="email"]',
|
|
'input[name="email"]',
|
|
'input[name="Email"]',
|
|
'input[id="email"]',
|
|
'input[autocomplete="email"]',
|
|
'input[autocomplete="username"]',
|
|
'input[name="identifier"]'
|
|
],
|
|
password: [
|
|
'input[type="password"]:not([name*="Confirm"]):not([name*="confirm"]):not([name*="Again"]):not([name*="again"])',
|
|
'input[name="password"]',
|
|
'input[name="Passwd"]',
|
|
'input[id="password"]',
|
|
'input[autocomplete="new-password"]'
|
|
],
|
|
password_confirm: [
|
|
'input[name="PasswdAgain"]',
|
|
'input[name="password_confirm"]',
|
|
'input[name="confirmPassword"]',
|
|
'input[name="passwordConfirmation"]',
|
|
'input[autocomplete="new-password"]:nth-of-type(2)'
|
|
],
|
|
first_name: [
|
|
'input[name="firstName"]',
|
|
'input[name="firstname"]',
|
|
'input[name="first_name"]',
|
|
'input[autocomplete="given-name"]'
|
|
],
|
|
last_name: [
|
|
'input[name="lastName"]',
|
|
'input[name="lastname"]',
|
|
'input[name="last_name"]',
|
|
'input[autocomplete="family-name"]'
|
|
],
|
|
username: [
|
|
'input[name="username"]',
|
|
'input[name="Username"]',
|
|
'input[id="username"]',
|
|
'input[autocomplete="username"]'
|
|
]
|
|
};
|
|
return map[key] || [];
|
|
}
|
|
|
|
function boot() {
|
|
loadConfig(function (j) {
|
|
cfg = j;
|
|
if (!cfg) return;
|
|
runFill();
|
|
var obs = new MutationObserver(function () { runFill(); });
|
|
obs.observe(document.documentElement || document.body, {
|
|
childList: true,
|
|
subtree: true
|
|
});
|
|
var timer = setInterval(function () {
|
|
runFill();
|
|
if (Date.now() - startedAt > MAX_MS) clearInterval(timer);
|
|
}, 900);
|
|
});
|
|
}
|
|
|
|
if (document.readyState === "loading") {
|
|
document.addEventListener("DOMContentLoaded", boot);
|
|
} else {
|
|
boot();
|
|
}
|
|
})();
|