[object Object]

← back to Dw Theme Toggle

viewer: send proper HTTP status + JSON content-type on API error responses

76b1e5d1eeab83e804f3b283085f24093178922e · 2026-05-18 20:16:48 -0700 · SteveStudio2

Files touched

Diff

commit 76b1e5d1eeab83e804f3b283085f24093178922e
Author: SteveStudio2 <steve@designerwallcoverings.com>
Date:   Mon May 18 20:16:48 2026 -0700

    viewer: send proper HTTP status + JSON content-type on API error responses
---
 viewer.js | 28 +++++++++++++++-------------
 1 file changed, 15 insertions(+), 13 deletions(-)

diff --git a/viewer.js b/viewer.js
index c258bc6..7bc75c0 100644
--- a/viewer.js
+++ b/viewer.js
@@ -187,6 +187,11 @@ const HTML = `<!doctype html>
 </body>
 </html>`;
 
+function sendJSON(res, status, obj) {
+  res.writeHead(status, { 'Content-Type': 'application/json' });
+  res.end(JSON.stringify(obj));
+}
+
 const server = http.createServer(async (req, res) => {
   try {
     if (req.url === '/' || req.url === '/index.html') {
@@ -205,37 +210,34 @@ const server = http.createServer(async (req, res) => {
     }
     if (req.method === 'POST' && req.url === '/api/themes') {
       const { domain, token } = JSON.parse(await readBody(req));
-      if (!domain || !token) return res.end(JSON.stringify({ ok: false, error: 'domain and token required' }));
+      if (!domain || !token) return sendJSON(res, 400, { ok: false, error: 'domain and token required' });
       const r = await shopifyFetch(domain, token, '/themes.json');
       const txt = await r.text();
-      let parsed; try { parsed = JSON.parse(txt); } catch { return res.end(JSON.stringify({ ok: false, error: 'non-JSON: ' + txt.slice(0,200) })); }
-      if (!r.ok) return res.end(JSON.stringify({ ok: false, error: 'HTTP ' + r.status + ': ' + (parsed.errors || JSON.stringify(parsed)).toString().slice(0,400) }));
-      res.writeHead(200, { 'Content-Type': 'application/json' });
-      return res.end(JSON.stringify({ ok: true, themes: (parsed.themes || []).map(t => ({ id: t.id, role: t.role, name: t.name })) }));
+      let parsed; try { parsed = JSON.parse(txt); } catch { return sendJSON(res, 502, { ok: false, error: 'non-JSON: ' + txt.slice(0,200) }); }
+      if (!r.ok) return sendJSON(res, r.status, { ok: false, error: 'HTTP ' + r.status + ': ' + (parsed.errors || JSON.stringify(parsed)).toString().slice(0,400) });
+      return sendJSON(res, 200, { ok: true, themes: (parsed.themes || []).map(t => ({ id: t.id, role: t.role, name: t.name })) });
     }
     if (req.method === 'POST' && req.url === '/api/push') {
       const { domain, token, themeId } = JSON.parse(await readBody(req));
-      if (!domain || !token || !themeId) return res.end(JSON.stringify({ ok: false, error: 'domain, token, themeId required' }));
+      if (!domain || !token || !themeId) return sendJSON(res, 400, { ok: false, error: 'domain, token, themeId required' });
       const snippetBody = fs.readFileSync(SNIPPET_PATH, 'utf8');
       const r = await shopifyFetch(domain, token, '/themes/' + themeId + '/assets.json', {
         method: 'PUT',
         body: JSON.stringify({ asset: { key: 'snippets/theme-toggle.liquid', value: snippetBody } }),
       });
       const txt = await r.text();
-      let parsed; try { parsed = JSON.parse(txt); } catch { return res.end(JSON.stringify({ ok: false, error: 'non-JSON: ' + txt.slice(0,200) })); }
-      if (!r.ok) return res.end(JSON.stringify({ ok: false, error: 'HTTP ' + r.status + ': ' + (parsed.errors || JSON.stringify(parsed)).toString().slice(0,400) }));
-      res.writeHead(200, { 'Content-Type': 'application/json' });
-      return res.end(JSON.stringify({ ok: true, asset: parsed.asset }));
+      let parsed; try { parsed = JSON.parse(txt); } catch { return sendJSON(res, 502, { ok: false, error: 'non-JSON: ' + txt.slice(0,200) }); }
+      if (!r.ok) return sendJSON(res, r.status, { ok: false, error: 'HTTP ' + r.status + ': ' + (parsed.errors || JSON.stringify(parsed)).toString().slice(0,400) });
+      return sendJSON(res, 200, { ok: true, asset: parsed.asset });
     }
     if (req.method === 'POST' && req.url === '/api/save-token') {
       const { token } = JSON.parse(await readBody(req));
-      if (!token) return res.end(JSON.stringify({ ok: false, error: 'token required' }));
+      if (!token) return sendJSON(res, 400, { ok: false, error: 'token required' });
       const last4 = token.slice(-4);
       const cli = path.join(process.env.HOME, 'Projects/secrets-manager/cli.js');
       exec('node ' + JSON.stringify(cli) + ' add SHOPIFY_ADMIN_TOKEN ' + JSON.stringify(token), { timeout: 30000 }, (err, stdout, stderr) => {
         const out = (stdout || '') + (stderr || '');
-        res.writeHead(200, { 'Content-Type': 'application/json' });
-        res.end(JSON.stringify({ ok: !err, last4, routes: out.slice(0, 600), error: err ? err.message : null }));
+        sendJSON(res, err ? 500 : 200, { ok: !err, last4, routes: out.slice(0, 600), error: err ? err.message : null });
       });
       return;
     }

← fcb360c snapshot: 1 file(s) changed, +1 new  ·  back to Dw Theme Toggle  ·  (newest)