diff --git a/server/routes/widgets.js b/server/routes/widgets.js index c180165..7967c3f 100644 --- a/server/routes/widgets.js +++ b/server/routes/widgets.js @@ -1,7 +1,34 @@ const express = require('express'); const router = express.Router(); +const fs = require('fs'); +const path = require('path'); const { v4: uuidv4 } = require('uuid'); const { db } = require('../db/database'); +const appConfig = require('../config'); + +// For preview only: inline /api/content/:id/file and /thumbnail URLs as data URIs, +// scoped to the current user. Lets the srcdoc preview iframe show logos/bg images +// before the widget is saved (post-save they're reachable via the widget-reference gate). +const MAX_INLINE_BYTES = 10 * 1024 * 1024; // 10MB cap — base64 expands ~1.33x +const MIME_RE = /^image\/[a-zA-Z0-9.+-]+$/; +function inlineUserContent(html, userId) { + return html.replace(/\/api\/content\/([a-f0-9-]+)\/(file|thumbnail)/gi, (match, id, kind) => { + const c = db.prepare('SELECT filepath, thumbnail_path, mime_type, user_id FROM content WHERE id = ?').get(id); + if (!c || c.user_id !== userId) return match; + const filename = kind === 'thumbnail' ? c.thumbnail_path : c.filepath; + if (!filename) return match; + const mime = kind === 'thumbnail' ? 'image/jpeg' : c.mime_type; + if (!mime || !MIME_RE.test(mime)) return match; + const safe = path.resolve(appConfig.contentDir, path.basename(filename)); + if (!safe.startsWith(path.resolve(appConfig.contentDir))) return match; + try { + const st = fs.statSync(safe); + if (!st.isFile() || st.size > MAX_INLINE_BYTES) return match; + const buf = fs.readFileSync(safe); + return `data:${mime};base64,${buf.toString('base64')}`; + } catch { return match; } + }); +} // Escape HTML to prevent XSS function escapeHtml(str) { @@ -89,37 +116,37 @@ router.delete('/:id', (req, res) => { res.json({ success: true }); }); +const KNOWN_WIDGET_TYPES = new Set(['clock','weather','rss','text','webpage','social','directory-board']); +function renderWidgetHtml(type, config) { + config = config || {}; + switch (type) { + case 'clock': return renderClock(config); + case 'weather': return renderWeather(config); + case 'rss': return renderRSS(config); + case 'text': return renderText(config); + case 'webpage': return renderWebpage(config); + case 'social': return renderSocial(config); + case 'directory-board': return renderDirectoryBoard(config); + default: return '