← back to Dw Pitch Followup
Frontend: build API URLs from location.host so fetches work even when page opened with inline credentials
8a5b1ac4cd1ffc67c14b41fbc5837ae6e368c421 · 2026-07-07 13:35:12 -0700 · Steve Abrams
Files touched
Diff
commit 8a5b1ac4cd1ffc67c14b41fbc5837ae6e368c421
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Jul 7 13:35:12 2026 -0700
Frontend: build API URLs from location.host so fetches work even when page opened with inline credentials
---
public/index.html | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/public/index.html b/public/index.html
index deb9ddd..4901d40 100644
--- a/public/index.html
+++ b/public/index.html
@@ -83,6 +83,10 @@
<script>
const LISTS = ['list1','list2','list3'];
+// Build fetch URLs from host only — never inherit credentials from the page URL.
+// If the tab was opened as http://user:pass@host/, a relative fetch('/api/…')
+// throws "URL that includes credentials"; rebuilding from location.host strips them.
+const API = p => `${location.protocol}//${location.host}${p}`;
const TAB_META = {
list1:{title:'Samples Sent ≥90%', defn:'Projects where ≥90% of the samples the client ordered have shipped — they have the goods in hand; follow up to convert.'},
list2:{title:'Invoiced · No Order', defn:'Clients invoiced in the last ~4 months whose invoices were all sample-only (no order over $50) — never converted to a firm order.'},
@@ -188,7 +192,7 @@ async function genLetter(card, opts={}){
subj.textContent=regen?'regenerating…':'composing…'; ta.value=''; vlabel.textContent='';
if(rbtn){ rbtn.disabled=true; rbtn.textContent='…'; }
try{
- const res = await fetch('/api/letter',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({list:cur,row:r,seed,showMfr:showMfr()})});
+ const res = await fetch(API('/api/letter'),{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({list:cur,row:r,seed,showMfr:showMfr()})});
const j = await res.json();
if(j.error) throw new Error(j.error);
subj.textContent='Subject: '+j.subject; ta.value=j.body;
@@ -261,7 +265,7 @@ document.getElementById('exportBtn').addEventListener('click',()=>{
io = new IntersectionObserver(es=>{ if(es.some(x=>x.isIntersecting)) renderChunk(); }, {rootMargin:'400px'});
io.observe(document.getElementById('sentinel'));
-fetch('/api/lists').then(r=>r.json()).then(j=>{
+fetch(API('/api/lists')).then(r=>r.json()).then(j=>{
DATA=j; cur = LS('lastTab','list1'); if(!LISTS.includes(cur)) cur='list1';
document.getElementById('built').textContent = 'built '+new Date(j.generatedAt).toLocaleString();
switchTab(cur);
← 0524e78 Skip Basic Auth for direct-loopback access so the localhost
·
back to Dw Pitch Followup
·
Redesign viewer: 3-column board (all lists at once) + List/G 5d7759e →