clawvault/extension/content.js

125 lines
3.4 KiB
JavaScript

// ClawVault Content Script
// Detect login forms and notify background
function detectForms() {
const forms = document.querySelectorAll('form');
let loginForms = 0;
forms.forEach(form => {
const hasPassword = form.querySelector('input[type="password"]');
const hasUsername = form.querySelector('input[type="text"], input[type="email"], input[name*="user"], input[name*="email"], input[name*="login"]');
if (hasPassword || hasUsername) {
loginForms++;
}
});
// Also check for standalone password fields
const standalonePasswords = document.querySelectorAll('input[type="password"]:not(form input)');
loginForms += standalonePasswords.length;
chrome.runtime.sendMessage({ action: 'formsDetected', count: loginForms });
return loginForms;
}
// Get all form fields for mapping
function getFormFields() {
const fields = [];
const inputs = document.querySelectorAll('input:not([type="hidden"]):not([type="submit"]):not([type="button"])');
inputs.forEach(input => {
const label = findLabel(input);
fields.push({
selector: getSelector(input),
label: label,
type: input.type,
name: input.name,
placeholder: input.placeholder,
autocomplete: input.autocomplete
});
});
return fields;
}
// Find label for an input
function findLabel(input) {
// Check for associated label
if (input.id) {
const label = document.querySelector(`label[for="${input.id}"]`);
if (label) return label.textContent.trim();
}
// Check for wrapping label
const parent = input.closest('label');
if (parent) {
const text = parent.textContent.replace(input.value, '').trim();
if (text) return text;
}
// Check aria-label
if (input.getAttribute('aria-label')) {
return input.getAttribute('aria-label');
}
// Use placeholder or name as fallback
return input.placeholder || input.name || input.type;
}
// Generate a unique selector for an element
function getSelector(el) {
if (el.id) return '#' + el.id;
if (el.name) return `[name="${el.name}"]`;
// Build a path selector
const path = [];
while (el && el !== document.body) {
let selector = el.tagName.toLowerCase();
if (el.className) {
selector += '.' + el.className.trim().split(/\s+/).join('.');
}
path.unshift(selector);
el = el.parentElement;
}
return path.join(' > ');
}
// Fill fields by selector
function fillFields(fields) {
Object.entries(fields).forEach(([label, selector]) => {
try {
const el = document.querySelector(selector);
if (el) {
el.value = label; // label here is actually the value
el.dispatchEvent(new Event('input', { bubbles: true }));
el.dispatchEvent(new Event('change', { bubbles: true }));
}
} catch (e) {
console.error('ClawVault: Failed to fill field', selector, e);
}
});
}
// Listen for messages from background
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === 'fillFields') {
fillFields(request.fields);
sendResponse({ success: true });
}
if (request.action === 'getFormFields') {
const fields = getFormFields();
sendResponse({ success: true, fields });
}
});
// Detect forms on page load
setTimeout(detectForms, 500);
// Re-detect on dynamic content changes
const observer = new MutationObserver(() => {
detectForms();
});
observer.observe(document.body, { childList: true, subtree: true });