mirror of
https://github.com/screentinker/screentinker.git
synced 2026-06-15 02:33:15 -06:00
fix(branding): inject instance branding into the app shell, no default flash (#76)
A never-visited org had no cached white-label, so brand-prime fell through to the ScreenTinker default baked into the static index.html and flashed it before branding.js fetched the org brand. Now the /app route injects the resolved instance / custom-domain branding into the shell as a <meta name="ssr-brand"> (CSP blocks inline <script>, so a meta carries it), and brand-prime applies that as the fallback when the per-workspace brand is not cached yet - so the page paints the configured brand on first load instead of ScreenTinker. - server.js: /app resolves branding (publicBranding strips internal columns) and injects the HTML-escaped JSON as a meta tag; falls back to plain sendFile on any error so branding can never break the app shell. - brand-prime.js: read meta[name=ssr-brand] when there is no rd_branding_<ws>. Verified: the meta carries the resolved brand (default ScreenTinker and a platform-default white-label), internal columns do not leak, 66 unit tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
53e32d31e2
commit
4d81bb112f
|
|
@ -16,6 +16,16 @@
|
|||
} catch (e) { /* malformed token -> treat as no workspace */ }
|
||||
|
||||
var wl = JSON.parse(localStorage.getItem('rd_branding_' + ws) || 'null');
|
||||
if (!wl) {
|
||||
// #76: no per-workspace cache yet (e.g. a never-visited org). Fall back to
|
||||
// the server-injected instance / custom-domain branding so the page paints
|
||||
// the configured brand instead of flashing the ScreenTinker default;
|
||||
// branding.js then fetches and caches the workspace-specific brand.
|
||||
try {
|
||||
var ssr = document.querySelector('meta[name="ssr-brand"]');
|
||||
if (ssr && ssr.content) wl = JSON.parse(ssr.content);
|
||||
} catch (e) { /* ignore */ }
|
||||
}
|
||||
if (!wl) return;
|
||||
|
||||
var root = document.documentElement;
|
||||
|
|
|
|||
|
|
@ -154,9 +154,25 @@ app.get('/', (req, res) => {
|
|||
res.sendFile(path.join(config.frontendDir, 'landing.html'));
|
||||
});
|
||||
|
||||
// Dashboard app
|
||||
// Dashboard app. Inject the resolved instance / custom-domain branding into the
|
||||
// shell as a <meta> (#76) so brand-prime can apply it before first paint when the
|
||||
// per-workspace brand is not cached yet - no ScreenTinker flash on a never-visited
|
||||
// org. CSP blocks inline <script>, so the brand rides in a <meta> that brand-prime
|
||||
// reads. Falls back to a plain send of the shell if anything goes wrong.
|
||||
app.get('/app', (req, res) => {
|
||||
res.sendFile(path.join(config.frontendDir, 'index.html'));
|
||||
const file = path.join(config.frontendDir, 'index.html');
|
||||
try {
|
||||
const { db } = require('./db/database');
|
||||
const { resolveBranding, publicBranding } = require('./lib/branding');
|
||||
const brand = publicBranding(resolveBranding(db, { domain: (req.hostname || '').toString() }));
|
||||
const attr = JSON.stringify(brand)
|
||||
.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>');
|
||||
const html = fs.readFileSync(file, 'utf8')
|
||||
.replace('</head>', ' <meta name="ssr-brand" content="' + attr + '">\n</head>');
|
||||
res.type('html').send(html);
|
||||
} catch (e) {
|
||||
res.sendFile(file);
|
||||
}
|
||||
});
|
||||
|
||||
// Sitemap and robots — served explicitly so the Content-Type is guaranteed
|
||||
|
|
|
|||
Loading…
Reference in a new issue