// Open Side Panel when the toolbar icon is clicked chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true }).catch(console.error); // Rebuild Context Menu on load chrome.storage.local.get(['activeClient']).then(res => { if (res.activeClient) updateContextMenus(res.activeClient); }); // Watch for changes to the active client to update the right-click menu chrome.storage.onChanged.addListener((changes) => { if (changes.activeClient) { updateContextMenus(changes.activeClient.newValue); } }); // Handle right-click menu clicks chrome.contextMenus.onClicked.addListener((info, tab) => { if (info.menuItemId.startsWith("supa_")) { const base64 = info.menuItemId.replace("supa_", ""); try { const value = decodeURIComponent(escape(atob(base64))); chrome.tabs.sendMessage(tab.id, { action: "paste_to_focused", value }).catch(err => { console.error("Paste failed. Make sure you are clicking on an editable field.", err); }); } catch(e) { console.error(e); } } }); function updateContextMenus(client) { chrome.contextMenus.removeAll(() => { if (!client) return; chrome.contextMenus.create({ id: "supapaste_root", title: "SupaPaste", contexts: ["editable"] }); const usedIds = new Set(); const addMenu = (title, value) => { if (!value) return; let baseId = "supa_" + btoa(unescape(encodeURIComponent(value))).replace(/=/g, ''); let safeId = baseId; let counter = 1; while (usedIds.has(safeId)) { safeId = `${baseId}_${counter}`; counter++; } usedIds.add(safeId); chrome.contextMenus.create({ id: safeId, parentId: "supapaste_root", title: `${title}: ${value}`, contexts: ["editable"] }, () => { if (chrome.runtime.lastError) { console.warn("Menu creation warning:", chrome.runtime.lastError.message); } }); }; addMenu("First Name", client.FirstName || client.firstName); addMenu("Last Name", client.LastName || client.lastName); addMenu("Phone", client.Phone || client.phone || client.cellPhone || client.CellPhone); addMenu("Email", client.Email || client.email); addMenu("SSN", client.SocialSecurityNumber || client.SSN); addMenu("DOB", client.BirthDate || client.dateOfBirth || client.DOB); addMenu("Address", client.Address1 || client.addressLine1 || client.address1); addMenu("City", client.City || client.city); addMenu("State", client.State || client.state); addMenu("Zip", client.Zip || client.zipCode || client.zip); if (client.vehicles) { client.vehicles.forEach((v, i) => addMenu(`VIN ${i+1}`, v.VIN || v.vin)); } if (client.drivers) { client.drivers.forEach((d, i) => addMenu(`Driver DL ${i+1}`, d.LicenseNumber || d.licenseNumber)); } }); } chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { if (message?.action === 'search_nowcerts') { (async () => { try { const results = await searchInsureds(String(message.query || '').trim()); sendResponse({ ok: true, results }); } catch (err) { sendResponse({ ok: false, error: err?.message || String(err) }); } })(); return true; } if (message?.action === 'create_prospect') { (async () => { try { const payload = message.payload || {}; const apiResult = await createProspect(payload); const last = String(payload.lastName || payload.LastName || '').trim(); const q = last || String(payload.firstName || payload.FirstName || '').trim(); const results = q ? await searchInsureds(q) : []; sendResponse({ ok: true, result: apiResult, results }); } catch (err) { sendResponse({ ok: false, error: err?.message || String(err) }); } })(); return true; } if (message?.action === 'get_full_client') { (async () => { try { const full = await getFullClientData(String(message.insuredId || '').trim()); sendResponse({ ok: true, client: full }); } catch (err) { sendResponse({ ok: false, error: err?.message || String(err) }); } })(); return true; } }); function odataEscapeString(s) { return String(s || '').replace(/'/g, "''"); } async function searchInsureds(query) { if (!query) return []; const storage = await chrome.storage.local.get(['ncApiToken']); const token = storage.ncApiToken; if (!token) throw new Error('Missing API Token, please sign in in Settings.'); const q = odataEscapeString(query); const url = `https://api.nowcerts.com/api/InsuredList()?$filter=(startswith(lastName,'${q}') or startswith(firstName,'${q}') or startswith(commercialName,'${q}'))&$top=20`; const response = await fetch(url, { method: 'GET', headers: { 'Authorization': `Bearer ${token}`, 'Accept': 'application/json' }}); if (!response.ok) { if (response.status === 401) throw new Error('Unauthorized (401). Token expired or invalid.'); const text = await response.text().catch(() => ''); throw new Error(`API Error ${response.status}: ${text || response.statusText}`); } const json = await response.json(); const arr = Array.isArray(json?.value) ? json.value : (Array.isArray(json) ? json : []); return arr.map((x) => { const first = x.FirstName || x.firstName || ''; const last = x.LastName || x.lastName || ''; const commercial = x.commercialName || x.CommercialName || ''; const full = x.FullName || x.fullName || x.Name || x.name || commercial || `${first} ${last}`.trim(); return { ...x, FullName: full, Email: x.Email || x.email || x.Email1 || x.email1 || x.PrimaryEmail || x.primaryEmail || '', Phone: x.Phone || x.phone || x.CellPhone || x.cellPhone || x.PrimaryPhone || x.primaryPhone || '', Id: x.Id || x.id || x.InsuredId || x.insuredId || x.EntityId || x.entityId || x.QuoteApplicationInsuredId || x.quoteApplicationInsuredId || '' }; }); } async function createProspect(input) { const storage = await chrome.storage.local.get(['ncApiToken']); const token = storage.ncApiToken; if (!token) throw new Error('Missing API Token.'); const body = { FirstName: String(input.firstName || '').trim(), LastName: String(input.lastName || '').trim(), Email: String(input.email || '').trim(), Phone: String(input.phone || '').trim(), Address1: String(input.street || '').trim(), City: String(input.city || '').trim(), State: String(input.state || '').trim(), Zip: String(input.zip || '').trim(), }; if (!body.FirstName || !body.LastName) throw new Error('First name and last name are required.'); let lastErr = null; for (const url of ['https://api.nowcerts.com/api/Insured/OnlyInsertInsured', 'https://api.nowcerts.com/api/Insured/Insert']) { try { const res = await fetch(url, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Accept': 'application/json', 'Content-Type': 'application/json'}, body: JSON.stringify(body) }); if (!res.ok) { if (res.status === 404) continue; if (res.status === 401) throw new Error('Unauthorized.'); throw new Error(`API Error ${res.status}`); } return await res.json().catch(async () => ({ message: await res.text() })); } catch (e) { lastErr = e; } } throw lastErr || new Error('Create prospect failed'); } async function getFullClientData(insuredId) { if (!insuredId) throw new Error('No insured ID provided'); const storage = await chrome.storage.local.get(['ncApiToken']); const token = storage.ncApiToken; if (!token) throw new Error('Missing API Token.'); const headers = { 'Authorization': `Bearer ${token}`, 'Accept': 'application/json' }; // 1. Details const detailRes = await fetch(`https://api.nowcerts.com/api/InsuredDetailList?key=${insuredId}&Active=true&showAll=true`, { method: 'GET', headers }); if (!detailRes.ok) throw new Error(`InsuredDetailList failed (${detailRes.status})`); const detailJson = await detailRes.json(); let insured = Array.isArray(detailJson?.value) ? detailJson.value[0] : Array.isArray(detailJson) ? detailJson[0] : detailJson || {}; // 2. Policies let policies = []; try { const polRes = await fetch('https://api.nowcerts.com/api/Insured/InsuredPolicies', { method: 'POST', headers: { ...headers, 'Content-Type': 'application/json' }, body: JSON.stringify({ insuredDataBaseId: [insuredId] }) }); if (polRes.ok) { const polJson = await polRes.json(); policies = Array.isArray(polJson) ? polJson : (polJson?.value || []); } } catch (e) { console.warn('Policies fetch failed', e); } let vehicles = []; let drivers = []; if (policies.length > 0) { const policyIds = policies.map(p => p.DatabaseId || p.databaseId || p.PolicyDatabaseId || '').filter(Boolean); if (policyIds.length) { // 3. Vehicles try { const vehRes = await fetch('https://api.nowcerts.com/api/Policy/PolicyVehicles', { method: 'POST', headers: { ...headers, 'Content-Type': 'application/json' }, body: JSON.stringify({ policyDataBaseId: policyIds }) }); if (vehRes.ok) vehicles = await vehRes.json(); } catch (e) { console.warn('Vehicles fetch failed', e); } // 4. Drivers try { const drvRes = await fetch('https://api.nowcerts.com/api/Policy/PolicyDrivers', { method: 'POST', headers: { ...headers, 'Content-Type': 'application/json' }, body: JSON.stringify({ policyDataBaseId: policyIds }) }); if (drvRes.ok) drivers = await drvRes.json(); } catch (e) { console.warn('Drivers fetch failed', e); } } } // Normalize all fields to consistent keys so content.js always finds them return { ...insured, FullName: insured.FullName || insured.fullName || `${insured.FirstName || insured.firstName || ''} ${insured.LastName || insured.lastName || ''}`.trim(), FirstName: insured.FirstName || insured.firstName || '', LastName: insured.LastName || insured.lastName || '', Email: insured.Email || insured.email || '', Phone: insured.Phone || insured.phone || insured.cellPhone || insured.CellPhone || '', Address1: insured.Address1 || insured.address1 || insured.addressLine1 || insured.StreetAddress1 || '', Address2: insured.Address2 || insured.address2 || insured.addressLine2 || insured.StreetAddress2 || '', City: insured.City || insured.city || '', State: insured.State || insured.state || insured.StateAbbreviation || insured.stateAbbreviation || '', Zip: insured.Zip || insured.zip || insured.zipCode || insured.ZipCode || '', BirthDate: insured.BirthDate || insured.dateOfBirth || insured.DateOfBirth || insured.DOB || '', SSN: insured.SocialSecurityNumber || insured.SSN || insured.socialSecurityNumber || insured.TaxId || '', Id: insured.Id || insured.id || insured.DatabaseId || insuredId, policies, vehicles, drivers }; }