[object Object]

← back to Naturaltextilewallpaper

Vendor-neutral image proxy via _shared/image-proxy: hero+detail render + /api/products+/api/sliders emit /img/<token>, server streams CDN bytes. Kills image_url vendor-name leak (279 Wolf Gordon products). Proven local: 0 image/cdn leaks, /img 200. (handle/sku concatenated-vendor TEXT leak is separate/pre-existing.)

2895d20ab502e6662b8aae415e4d33edeea971a2 · 2026-06-03 14:30:24 -0700 · Steve Abrams

Files touched

Diff

commit 2895d20ab502e6662b8aae415e4d33edeea971a2
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Jun 3 14:30:24 2026 -0700

    Vendor-neutral image proxy via _shared/image-proxy: hero+detail render + /api/products+/api/sliders emit /img/<token>, server streams CDN bytes. Kills image_url vendor-name leak (279 Wolf Gordon products). Proven local: 0 image/cdn leaks, /img 200. (handle/sku concatenated-vendor TEXT leak is separate/pre-existing.)
---
 server.js | 16 ++++++++++++----
 1 file changed, 12 insertions(+), 4 deletions(-)

diff --git a/server.js b/server.js
index dc19b22..71932e7 100644
--- a/server.js
+++ b/server.js
@@ -10,6 +10,8 @@ const express = require('express');
 const helmet = require('helmet');
 const path = require('path');
 const fs = require('fs');
+const { createImgProxy } = require('../_shared/image-proxy');
+const IMGP = createImgProxy();   // vendor-neutral /img/<token> image proxy
 // Admin catalog (PG-backed) is opt-in via MICROSITE_ADMIN_ENABLED=true. Prod
 // stays static-only (data/products.json) so Kamatera doesn't need `pg` or the
 // _shared/ tree. Dev (Mac2) flips the flag on to use dw_unified + /admin/catalog.
@@ -228,6 +230,10 @@ app.use(require('../_shared/api-vendor-redact'));
 
 app.use(express.static(path.join(__dirname, 'public')));
 
+// Vendor-neutral image proxy — render + APIs emit /img/<token>; this streams the
+// real CDN bytes so the vendor filename never reaches the client. (see _shared/image-proxy.js)
+IMGP.mount(app);
+
 // --- API: products (JSON for infinite scroll) ---------------------------
 app.get('/api/products', (req, res) => {
   const { q, material, sort='newest', page=1, limit=24 } = req.query;
@@ -250,7 +256,7 @@ app.get('/api/products', (req, res) => {
   // slug = vendor-scrubbed handle for the customer-facing /p/ href + visible
   // SKU. The REAL handle is kept on the item so the main-store buy link
   // (designerwallcoverings.com/products/<real-handle>) still resolves.
-  const items = list.slice(start, start+lim).map(p => ({ ...p, slug: redactVendorSlug(p.handle) }));
+  const items = list.slice(start, start+lim).map(p => ({ ...IMGP.scrubProduct(p), slug: redactVendorSlug(p.handle) }));
   res.json({ total, page:pg, limit:lim, pages: Math.ceil(total/lim), sort, items });
 });
 
@@ -259,7 +265,7 @@ app.get('/api/sliders', (req, res) => {
   const rails = [];
   for (const m of MATERIALS) {
     const items = PRODUCTS.filter(p => p.material === m.key).slice(0, 12);
-    if (items.length >= 4) rails.push({ material: m.key, label: m.label, items });
+    if (items.length >= 4) rails.push({ material: m.key, label: m.label, items: items.map(IMGP.scrubProduct) });
   }
   res.json({ rails });
 });
@@ -570,7 +576,7 @@ function renderIndex(q, material, sort) {
   ${q==='' && material==='all' ? `
   <section class="hero">
     <div class="hero-image-wrap">
-      ${heroProduct ? `<img src="${escHtml(heroProduct.image_url)}" alt="Natural fiber weave detail — ${escHtml(heroProduct.title)}" class="hero-img" loading="eager">` : ''}
+      ${heroProduct ? `<img src="${escHtml(IMGP.proxyImg(heroProduct.image_url))}" alt="Natural fiber weave detail — ${escHtml(heroProduct.title)}" class="hero-img" loading="eager">` : ''}
       <div class="hero-overlay"></div>
     </div>
     <div class="hero-content">
@@ -752,7 +758,7 @@ function renderDetail(p, matLabel) {
   const buySlug = encodeURIComponent(redactVendorSlug(p.handle));
   const dw_url = `/buy/${buySlug}`;
   const sample_url = `/buy/${buySlug}?sample=1`;
-  const images = p.images && p.images.length > 1 ? p.images : [p.image_url];
+  const images = (p.images && p.images.length > 1 ? p.images : [p.image_url]).map(IMGP.proxyImg);
   return headHTML(
     `${p.title} — Free Memo Sample | Natural Textile Wallpaper`,
     `Explore ${p.title} — a ${matLabel} wallcovering available with free memo samples and trade service from Designer Wallcoverings. Order yours today.`
@@ -877,5 +883,7 @@ if (catalog) catalog.mount(app, { siteSlug: SITE_SLUG, rails: SITE_RAILS });
       console.log(`[NTW] Loaded ${PRODUCTS.length} from data/products.json (static mode)`);
     } catch (_) { PRODUCTS = []; }
   }
+  IMGP.registerAll(PRODUCTS);   // build the /img token->CDN map for every product image
+  console.log(`[NTW] image-proxy map: ${IMGP.size()} urls`);
   app.listen(PORT, () => console.log(`[NTW] Running on port ${PORT} — ${PRODUCTS.length} products`));
 })();

← c2bd7bc render: route buy CTA + main-store anchor + card sample thro  ·  back to Naturaltextilewallpaper  ·  Scrub residual vendor names from products.json (title/vendor 2f5369f →