chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { // NEW: Extract structure for AI mapping if (message.action === "extract_form_structure") { const inputs = document.querySelectorAll('input:not([type="hidden"]):not([disabled]), select:not([disabled]), textarea:not([disabled])'); const fields = Array.from(inputs).map(el => ({ id: el.id, name: el.name, placeholder: el.placeholder, label: findLabelFor(el) })).filter(x => x.id || x.name || x.label); sendResponse({ fields }); return true; } // NEW: Show cute boxes when smart map exists if (message.action === "show_cute_boxes") { injectCuteBoxes(message.smartMap); sendResponse({ ok: true }); } if (message.action === "fill_form") { // Look up both active client and smart map const hostname = window.location.hostname; chrome.storage.local.get(['activeClient', `smartMap_${hostname}`], (res) => { if (res.activeClient) { fillInputs(res.activeClient, res[`smartMap_${hostname}`]); } else { alert("Please select a client in the Carrier Bridge side panel first."); } }); } if (message.action === "paste_to_focused") { const activeEl = document.activeElement; if (!activeEl) { alert("No field focused. Click into a form field first!"); return; } const isInput = activeEl.tagName === 'INPUT' || activeEl.tagName === 'TEXTAREA'; const isEditable = activeEl.isContentEditable || activeEl.getAttribute('contenteditable') === 'true'; if (isInput || isEditable) { const value = message.value || ''; if (isInput) { activeEl.value = value; } else if (isEditable) { document.execCommand('insertText', false, value); } ['input', 'change', 'keyup', 'blur', 'focus'].forEach(ev => { activeEl.dispatchEvent(new Event(ev, { bubbles: true })); }); const originalBg = activeEl.style.backgroundColor; activeEl.style.backgroundColor = '#a7f3d0'; activeEl.style.transition = 'background-color 0.7s'; setTimeout(() => { activeEl.style.backgroundColor = originalBg; }, 700); console.log(`Super Paste: "${value}" → ${activeEl.name || activeEl.id || activeEl.tagName || 'focused element'}`); } else { alert("Please focus an input, textarea, or editable area first."); } } }); function injectCuteBoxes(smartMap) { if (!smartMap) return; // Remove old boxes if they exist document.querySelectorAll('.supa-cute-box').forEach(e => e.remove()); const inputs = document.querySelectorAll('input, select, textarea'); inputs.forEach(input => { let matchedKey = null; for (const [key, selector] of Object.entries(smartMap)) { if (selector && (input.id === selector || input.name === selector)) { matchedKey = key; break; } } if (matchedKey) { const badge = document.createElement('span'); badge.className = 'supa-cute-box'; badge.textContent = `✨ ${matchedKey}`; badge.style.cssText = 'background: #10b981; color: white; font-size: 10px; padding: 2px 6px; border-radius: 4px; margin-left: 6px; font-weight: bold; box-shadow: 0 1px 3px rgba(0,0,0,0.2); vertical-align: middle; display: inline-block; pointer-events: none; z-index: 9999;'; // Insert the cute badge right after the input if (input.parentNode) { input.parentNode.insertBefore(badge, input.nextSibling); } } }); } function fillInputs(client, smartMap = null) { const inputs = document.querySelectorAll('input, select, textarea'); let filledCount = 0; let dobM = '', dobD = '', dobY = ''; const rawDob = client.BirthDate || client.DateOfBirth || client.dateOfBirth || client.DOB || ''; if (rawDob) { const d = new Date(rawDob); if (!isNaN(d.getTime())) { dobM = (d.getMonth() + 1).toString().padStart(2, '0'); dobD = d.getDate().toString().padStart(2, '0'); dobY = d.getFullYear().toString(); } } const map = { // Specific fields first to avoid generic 'name' matching 'NamedInsured' IDs 'emailaddress': client.Email || client.email || client.PrimaryEmail || '', 'email': client.Email || client.email || client.PrimaryEmail || '', 'phone': client.Phone || client.phone || client.CellPhone || client.cellPhone || client.PrimaryPhone || '', 'cell': client.CellPhone || client.cellPhone || client.MobilePhone || '', 'mobile': client.CellPhone || client.cellPhone || client.MobilePhone || '', 'homephone': client.HomePhone || client.phone || '', 'city': client.City || client.city || '', 'zip': client.Zip || client.zip || client.ZipCode || client.zipCode || client.PostalCode || '', 'postal': client.Zip || client.zip || client.ZipCode || client.zipCode || client.PostalCode || '', 'state': client.State || client.state || '', 'address': client.Address1 || client.Address || client.address || client.addressLine1 || '', 'addr': client.Address1 || client.Address || client.address || client.addressLine1 || '', 'street': client.Address1 || client.Address || client.address || client.addressLine1 || '', 'inputdate': formatDate(rawDob), 'dateofbirth': formatDate(rawDob), 'birthdate': formatDate(rawDob), 'birth': formatDate(rawDob), 'dob': formatDate(rawDob), 'mmddyyyy': formatDate(rawDob), 'dobmonth': dobM, 'birthmonth': dobM, 'monthofbirth': dobM, 'datemonth': dobM, 'mm': (c) => (c.includes('dob') || c.includes('birth') || (c.includes('date') && !c.includes('dateofbirth'))) ? dobM : '', 'dobday': dobD, 'birthday': dobD, 'dayofbirth': dobD, 'dateday': dobD, 'dd': (c) => (c.includes('dob') || c.includes('birth') || (c.includes('date') && !c.includes('dateofbirth'))) ? dobD : '', 'dobyear': dobY, 'birthyear': dobY, 'yearofbirth': dobY, 'dateyear': dobY, 'yyyy': (c) => (c.includes('dob') || c.includes('birth') || (c.includes('date') && !c.includes('dateofbirth'))) ? dobY : '', 'ssn': client.SSN || client.ssn || client.TaxId || client.taxId || '', 'taxid': client.SSN || client.ssn || client.TaxId || client.taxId || '', 'sin': client.SSN || client.ssn || '', 'license': client.DriverLicenseNumber || client.driverLicenseNumber || client.LicenseNumber || '', 'dlnumber': client.DriverLicenseNumber || client.driverLicenseNumber || client.LicenseNumber || '', 'dln': client.DriverLicenseNumber || client.driverLicenseNumber || client.LicenseNumber || '', 'gender': client.Gender || client.gender || client.Sex || client.sex || '', 'sex': client.Gender || client.gender || client.Sex || client.sex || '', // Generic name fields last 'first': client.FirstName || client.firstName || '', 'fname': client.FirstName || client.firstName || '', 'givenname': client.FirstName || client.firstName || '', 'given': client.FirstName || client.firstName || '', 'firstname': client.FirstName || client.firstName || '', 'last': client.LastName || client.lastName || '', 'lname': client.LastName || client.lastName || '', 'surname': client.LastName || client.lastName || '', 'familyname': client.LastName || client.lastName || '', 'lastname': client.LastName || client.lastName || '', 'middle': client.MiddleName || client.middleName || client.MI || '', 'mi': client.MiddleName || client.middleName || client.MI || '', 'fullname': client.FullName || '', // FIX: Ensure name field doesn't clash with NamedInsured 'name': (c) => c.includes('named') ? '' : (client.FullName || `${client.FirstName || ''} ${client.LastName || ''}`.trim()), 'effectivedate': formatDate(getFirstPolicyValue(client, ['EffectiveDate', 'effectiveDate', 'StartDate'])), 'startdate': formatDate(getFirstPolicyValue(client, ['EffectiveDate', 'effectiveDate', 'StartDate'])), 'expirationdate': formatDate(getFirstPolicyValue(client, ['ExpirationDate', 'expirationDate', 'ExpDate'])), 'expdate': formatDate(getFirstPolicyValue(client, ['ExpirationDate', 'expirationDate', 'ExpDate'])), 'carrier': getFirstPolicyValue(client, ['CarrierName', 'carrierName', 'CompanyName']), 'company': getFirstPolicyValue(client, ['CarrierName', 'carrierName', 'CompanyName']), 'vin': getFirstVehicleValue(client, ['VIN', 'vin']), 'vehicleyear': getFirstVehicleValue(client, ['Year', 'year', 'ModelYear']), 'vehiclemake': getFirstVehicleValue(client, ['Make', 'make']), 'vehiclemodel': getFirstVehicleValue(client, ['Model', 'model']), 'year': (c) => (c.includes('dob') || c.includes('birth')) ? '' : getFirstVehicleValue(client, ['Year', 'year', 'ModelYear']), 'make': getFirstVehicleValue(client, ['Make', 'make']), 'model': getFirstVehicleValue(client, ['Model', 'model']), }; inputs.forEach(input => { if (input.type === 'hidden' || input.readOnly || input.disabled) return; let smartMatchKey = null; if (smartMap) { for (const [key, selector] of Object.entries(smartMap)) { if (selector && (input.id === selector || input.name === selector)) { smartMatchKey = key.toLowerCase(); break; } } } let finalValue = null; let matchedKeyword = null; // --- 1. Try Smart Map First --- if (smartMatchKey && map[smartMatchKey]) { const val = map[smartMatchKey]; finalValue = typeof val === 'function' ? val('', input) : val; matchedKeyword = smartMatchKey; } // --- 2. Fallback to classic combined string check --- else { const name = (input.name || '').toLowerCase().replace(/[^a-z0-9]/g, ''); const id = (input.id || '').toLowerCase().replace(/[^a-z0-9]/g, ''); const ph = (input.placeholder || '').toLowerCase().replace(/[^a-z0-9]/g, ''); const labelText = findLabelFor(input).toLowerCase().replace(/[^a-z0-9]/g, ''); const aria = (input.getAttribute('aria-label') || '').toLowerCase().replace(/[^a-z0-9]/g, ''); const analytics = (input.getAttribute('analytics-id') || input.getAttribute('data-analytics-id') || '').toLowerCase().replace(/[^a-z0-9]/g, ''); const classes = (input.className || '').toLowerCase().replace(/[^a-z0-9]/g, ''); const matInput = (input.getAttribute('matinput') !== null ? 'matinput' : ''); const combined = name + id + ph + labelText + aria + analytics + classes + matInput; for (const [keyword, value] of Object.entries(map)) { if (combined.includes(keyword)) { finalValue = typeof value === 'function' ? value(combined, input) : value; if (finalValue) { matchedKeyword = keyword; break; } } } } if (finalValue) { // FIX: Masked inputs (like DOB on FAO) appear filled with __/__/____ const currentVal = input.value || ''; const isEffectivelyEmpty = currentVal.trim() === '' || currentVal.replace(/[_/\-\s]/g, '') === '' || currentVal.toLowerCase() === 'mm/dd/yyyy'; if (isEffectivelyEmpty) { // Focus the element first so Angular Material registers the interaction input.focus(); // Use native setter to bypass Angular/React value interception const nativeSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value') || Object.getOwnPropertyDescriptor(window.HTMLSelectElement.prototype, 'value') || Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value'); if (nativeSetter && nativeSetter.set) { nativeSetter.set.call(input, finalValue); } else { input.value = finalValue; } // Add 'keyup' to the events array so strict masks catch the change ['input', 'change', 'keyup', 'blur'].forEach(ev => { input.dispatchEvent(new Event(ev, { bubbles: true })); }); input.style.backgroundColor = '#d1fae5'; input.style.transition = 'background-color 0.6s'; setTimeout(() => { input.style.backgroundColor = ''; }, 1200); filledCount++; console.log(`Auto-filled: ${matchedKeyword} → "${finalValue}"`); } } }); // ─── Agent Exchange: Second Named Insured ──────────────────────────── if (isAgentExchangeCustomerPage()) { fillAgentExchangeSecondNamedInsured(client); } console.log(`Carrier Bridge: Auto-filled ${filledCount} fields.`); } // ──────────────────────────────────────────────────────────────────────────── // Agent Exchange — Second Named Insured // ──────────────────────────────────────────────────────────────────────────── function isAgentExchangeCustomerPage() { return ( window.location.href.includes('agentexchange.com') && window.location.pathname.includes('/Customer') ); } function detectRiskState() { try { const contentArea = document.getElementById('contentarea'); if (contentArea && typeof ko !== 'undefined') { const vm = ko.dataFor(contentArea); if (vm) { const state = vm.AutoRiskState || vm.HomeRiskState || vm.ResidenceRiskState || ''; if (state) return state; } } } catch (e) { /* ko not available */ } const stateEl = document.getElementById('selMailingState'); if (stateEl && stateEl.value) return stateEl.value; return ''; } function getSecondDriver(client) { if (client.drivers && client.drivers.length > 1) return client.drivers[1]; if (client.contacts && client.contacts.length > 1) return client.contacts[1]; if (client.householdMembers && client.householdMembers.length > 1) return client.householdMembers[1]; return null; } function setSelectValue(el, value) { if (!el || value === undefined || value === null) return false; el.value = value; ['change', 'blur'].forEach(ev => el.dispatchEvent(new Event(ev, { bubbles: true }))); if (typeof ko !== 'undefined') { try { ko.utils.triggerEvent(el, 'change'); } catch (e) {} } el.style.backgroundColor = '#d1fae5'; el.style.transition = 'background-color 0.6s'; setTimeout(() => { el.style.backgroundColor = ''; }, 1200); return true; } function setInputValue(el, value) { if (!el || value === undefined || value === null || value === '') return false; const nativeSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value'); if (nativeSetter && nativeSetter.set) { nativeSetter.set.call(el, value); } else { el.value = value; } ['input', 'change', 'blur'].forEach(ev => el.dispatchEvent(new Event(ev, { bubbles: true }))); if (typeof ko !== 'undefined') { try { ko.utils.triggerEvent(el, 'change'); } catch (e) {} } el.style.backgroundColor = '#d1fae5'; el.style.transition = 'background-color 0.6s'; setTimeout(() => { el.style.backgroundColor = ''; }, 1200); return true; } function fillAgentExchangeSecondNamedInsured(client) { const sniDropdown = document.getElementById('ddlSecondNamedInsured'); if (!sniDropdown) { console.log('Carrier Bridge: SNI dropdown not found — skipping SNI fill.'); return; } if (sniDropdown.value !== '0') { console.log(`Carrier Bridge: SNI already set ("${sniDropdown.value}") — skipping.`); return; } const secondDriver = getSecondDriver(client); if (!secondDriver) { console.log('Carrier Bridge: No second driver in client data — skipping SNI fill.'); return; } const riskState = detectRiskState(); console.log(`Carrier Bridge: Risk state = "${riskState}". Filling SNI with second driver...`); setSelectValue(sniDropdown, '-1'); setTimeout(() => fillSniFields(secondDriver, riskState), 450); } function fillSniFields(driver, riskState) { console.log('Carrier Bridge: Populating SNI fields...'); const get = (obj, ...keys) => { for (const k of keys) { const v = obj[k]; if (v !== undefined && v !== null && v !== '') return v; } return ''; }; let filled = []; const firstName = get(driver, 'FirstName', 'firstName', 'first_name', 'first'); const middleName = get(driver, 'MiddleName', 'middleName', 'middle_name', 'middle', 'MI'); const lastName = get(driver, 'LastName', 'lastName', 'last_name', 'last'); const suffix = get(driver, 'Suffix', 'suffix'); if (setInputValue(document.getElementById('SecondNamedInsured_FirstName'), firstName)) filled.push('FirstName'); if (setInputValue(document.getElementById('SecondNamedInsured_MiddleName'), middleName)) filled.push('MiddleName'); if (setInputValue(document.getElementById('SecondNamedInsured_LastName'), lastName)) filled.push('LastName'); const suffixEl = document.getElementById('SecondNamedInsured_Suffix'); if (suffixEl && suffix && setSelectValue(suffixEl, suffix)) filled.push('Suffix'); const gender = get(driver, 'Gender', 'gender', 'Sex', 'sex'); const genderEl = document.getElementById('selGender2'); if (genderEl && gender && setSelectValue(genderEl, gender)) filled.push('Gender'); const rawDob = get(driver, 'BirthDate', 'DateOfBirth', 'dateOfBirth', 'DOB', 'dob'); if (rawDob) { const dobFormatted = formatDate(rawDob); const dobEl = document.getElementById('txtDateOfBirth_2') || document.querySelector('[id^="txtDateOfBirth_"][id$="2"]'); if (dobEl && setInputValue(dobEl, dobFormatted)) filled.push('DOB'); } const licenseState = get(driver, 'DriverLicenseState', 'driverLicenseState', 'LicenseState', 'licenseState', 'DLState'); const effectiveLicenseState = licenseState || (riskState === 'NC' ? 'NC' : ''); const licenseStateEl = document.getElementById('selLicenseState2'); if (licenseStateEl && effectiveLicenseState && setSelectValue(licenseStateEl, effectiveLicenseState)) { filled.push('LicenseState'); } const licenseNum = get(driver, 'DriverLicenseNumber', 'driverLicenseNumber', 'LicenseNumber', 'licenseNumber', 'DLNumber', 'dlNumber'); const licenseNumEl = document.getElementById('licenseNumber2'); if (licenseNumEl && licenseNum && setInputValue(licenseNumEl, licenseNum)) filled.push('LicenseNumber'); if (filled.length > 0) { console.log(`Carrier Bridge: SNI filled — ${filled.join(', ')}`); } else { console.warn('Carrier Bridge: SNI dropdown set to "Other" but no second driver fields could be filled. Check driver data property names.'); } } // ──────────────────────────────────────────────────────────────── // Helpers // ──────────────────────────────────────────────────────────────── function formatDate(dateStr) { if (!dateStr) return ''; try { const d = new Date(dateStr); if (isNaN(d.getTime())) return dateStr; const mm = (d.getMonth() + 1).toString().padStart(2, '0'); const dd = d.getDate().toString().padStart(2, '0'); const yyyy = d.getFullYear(); return `${mm}/${dd}/${yyyy}`; } catch { return dateStr; } } function findLabelFor(input) { let text = ""; if (input.id) { const label = document.querySelector(`label[for="${input.id}"]`); if (label) text += label.innerText + " "; } const parentLabel = input.closest('label'); if (parentLabel) text += parentLabel.innerText + " "; try { const matLabel = input.closest('mat-form-field')?.querySelector('mat-label'); if (matLabel) text += matLabel.innerText + " "; } catch(e) {} if (input.getAttribute('aria-label')) { text += input.getAttribute('aria-label') + " "; } const prev = input.previousElementSibling; if (prev && (prev.tagName === 'LABEL' || prev.tagName === 'SPAN' || prev.tagName === 'MAT-LABEL')) { text += prev.innerText + " "; } return text.trim(); } function getFirstPolicyValue(client, possibleKeys) { if (!client?.policies?.length) return ''; for (const p of client.policies) { for (const k of possibleKeys) { if (p[k] || p[k.toLowerCase()] || p[k.charAt(0).toUpperCase() + k.slice(1)]) { return p[k] || p[k.toLowerCase()] || p[k.charAt(0).toUpperCase() + k.slice(1)]; } } } return ''; } function getFirstVehicleValue(client, possibleKeys) { if (!client?.vehicles?.length) return ''; for (const v of client.vehicles) { for (const k of possibleKeys) { if (v[k] || v[k.toLowerCase()] || v[k.charAt(0).toUpperCase() + k.slice(1)]) { return v[k] || v[k.toLowerCase()] || v[k.charAt(0).toUpperCase() + k.slice(1)]; } } } return ''; }