chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { if (message.action === "fill_form") { chrome.storage.local.get('activeClient', (res) => { if (res.activeClient) { fillInputs(res.activeClient); } 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 fillInputs(client) { 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 = { '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 || '', 'name': client.FullName || `${client.FirstName || ''} ${client.LastName || ''}`.trim(), '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 || '', 'dobmonth': dobM, 'birthmonth': dobM, 'monthofbirth': dobM, 'datemonth': dobM, 'mm': (c) => (c.includes('dob') || c.includes('birth') || c.includes('date')) ? dobM : '', 'dobday': dobD, 'birthday': dobD, 'dayofbirth': dobD, 'dateday': dobD, 'dd': (c) => (c.includes('dob') || c.includes('birth') || c.includes('date')) ? dobD : '', 'dobyear': dobY, 'birthyear': dobY, 'yearofbirth': dobY, 'dateyear': dobY, 'yyyy': (c) => (c.includes('dob') || c.includes('birth') || c.includes('date')) ? dobY : '', 'dob': formatDate(rawDob), 'dateofbirth': formatDate(rawDob), 'birthdate': formatDate(rawDob), '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 || '', 'address': client.Address || client.address || client.AddressLine1 || client.addressLine1 || '', 'addr': client.Address || client.address || client.AddressLine1 || client.addressLine1 || '', 'street': client.Address || client.address || client.AddressLine1 || client.addressLine1 || '', '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 || '', '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; 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 combined = name + id + ph + labelText + aria; for (const [keyword, value] of Object.entries(map)) { if (combined.includes(keyword)) { let finalValue = typeof value === 'function' ? value(combined, input) : value; if (!finalValue) continue; if (!input.value || input.value.trim() === '') { input.value = finalValue; ['input', 'change', '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: ${keyword} → "${finalValue}"`); break; } } } }); // ─── Agent Exchange: Second Named Insured ──────────────────────────── // Runs on the Customer page when a second driver exists in client data. // Selects "Other" in the SNI dropdown and fills the revealed fields. 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') ); } /** * Detects the risk state from the Knockout view model or from page elements. * Returns a 2-letter state code (e.g. "NC") or empty string. */ 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 */ } // Fallback: mailing state select const stateEl = document.getElementById('selMailingState'); if (stateEl && stateEl.value) return stateEl.value; return ''; } /** * Returns the second driver object from the client's data. * Checks drivers[], contacts[], and householdMembers[] arrays. */ 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; } /** * Sets a value using the native setter (bypasses framework value interceptors) * and fires the events needed to update Knockout observables. */ function setInputValue(el, value) { if (!el || value === undefined || value === null || value === '') return false; // Use native setter to trigger Knockout's subscription 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) {} } // Visual feedback el.style.backgroundColor = '#d1fae5'; el.style.transition = 'background-color 0.6s'; setTimeout(() => { el.style.backgroundColor = ''; }, 1200); return true; } /** * Entry point: checks whether we should fill the SNI section and kicks it off. * * Conditions to run: * - ddlSecondNamedInsured dropdown exists and is currently "0" (None) * - Client has a second driver in their data * * This runs for ALL states when there's a second driver; the NC detection is * informational (used to default the license state to NC if none is specified). */ function fillAgentExchangeSecondNamedInsured(client) { const sniDropdown = document.getElementById('ddlSecondNamedInsured'); if (!sniDropdown) { console.log('Carrier Bridge: SNI dropdown not found — skipping SNI fill.'); return; } // Only act if SNI is currently unset 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...`); // Step 1: Select "Other" (-1) — this triggers Knockout to reveal the name/detail fields setSelectValue(sniDropdown, '-1'); // Step 2: Wait for Knockout to render the revealed fields, then fill them setTimeout(() => fillSniFields(secondDriver, riskState), 450); } /** * Fills all Second Named Insured input fields from the second driver's data. * * Agent Exchange field IDs for the SNI section (from the Knockout template): * SecondNamedInsured_FirstName * SecondNamedInsured_MiddleName * SecondNamedInsured_LastName * SecondNamedInsured_Suffix (select) * selGender2 (select, index-based) * txtDateOfBirth_2 * selLicenseState2 (select, index-based) * licenseNumber2 */ function fillSniFields(driver, riskState) { console.log('Carrier Bridge: Populating SNI fields...'); // Resolve a value from multiple possible property names const get = (obj, ...keys) => { for (const k of keys) { const v = obj[k]; if (v !== undefined && v !== null && v !== '') return v; } return ''; }; let filled = []; // ─── Legal Name ─────────────────────────────────────────────────────── 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'); // ─── Gender ─────────────────────────────────────────────────────────── // Knockout renders index-2 element as selGender2 const gender = get(driver, 'Gender', 'gender', 'Sex', 'sex'); const genderEl = document.getElementById('selGender2'); if (genderEl && gender && setSelectValue(genderEl, gender)) filled.push('Gender'); // ─── Date of Birth ──────────────────────────────────────────────────── // Rendered as txtDateOfBirth_2 by Knockout's namedInsuredHtmlIdIndex() 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'); } // ─── License State ──────────────────────────────────────────────────── // Rendered as selLicenseState2 const licenseState = get(driver, 'DriverLicenseState', 'driverLicenseState', 'LicenseState', 'licenseState', 'DLState'); // For NC risk state quotes, default license state to NC if driver data doesn't specify one const effectiveLicenseState = licenseState || (riskState === 'NC' ? 'NC' : ''); const licenseStateEl = document.getElementById('selLicenseState2'); if (licenseStateEl && effectiveLicenseState && setSelectValue(licenseStateEl, effectiveLicenseState)) { filled.push('LicenseState'); } // ─── License Number ─────────────────────────────────────────────────── // Rendered as licenseNumber2 const licenseNum = get(driver, 'DriverLicenseNumber', 'driverLicenseNumber', 'LicenseNumber', 'licenseNumber', 'DLNumber', 'dlNumber'); const licenseNumEl = document.getElementById('licenseNumber2'); if (licenseNumEl && licenseNum && setInputValue(licenseNumEl, licenseNum)) filled.push('LicenseNumber'); // ─── Result ─────────────────────────────────────────────────────────── 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; return `${(d.getMonth()+1).toString().padStart(2,'0')}/${ d.getDate().toString().padStart(2,'0')}/${ d.getFullYear()}`; } 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 + " "; if (input.getAttribute('aria-label')) { text += input.getAttribute('aria-label') + " "; } const prev = input.previousElementSibling; if (prev && (prev.tagName === 'LABEL' || prev.tagName === 'SPAN')) { 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 ''; }