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