← 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 →