Fix missing QR on every member page — blocked by our own CSP
The QR on /p/<id> and /p/<id>/<slug> was drawn by an inline <script>, but the site sends `script-src 'self'` with no 'unsafe-inline'. The browser silently refused to run it, so the QR box rendered as an empty white square on every member page, not just Funnel Factory ones. Moved the bootstrap to /page-qr.js and pass the link via a data attribute, so no page data is interpolated into executable script and the CSP stays as strict as it is. Loosening script-src to fix this would have traded a site-wide security property for one widget. This matters more than it looks: these pages go on printed flyers and ad destinations, where an unscannable QR is discovered by the person holding the paper. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
// Draws the QR on a member's hosted page (/p/<id> and /p/<id>/<slug>).
|
||||
//
|
||||
// This lives in its own file rather than inline in the rendered HTML for one
|
||||
// reason: the site's CSP is `script-src 'self'` with no 'unsafe-inline', so an
|
||||
// inline bootstrap is silently blocked and the QR box renders as an empty white
|
||||
// square. Nothing errors visibly — the page just quietly loses its QR, which is
|
||||
// exactly the sort of failure nobody notices until a printed flyer or an ad
|
||||
// lands somewhere and cannot be scanned.
|
||||
//
|
||||
// The link comes from a data attribute so no page data is ever interpolated
|
||||
// into executable script.
|
||||
(function () {
|
||||
'use strict';
|
||||
function draw() {
|
||||
var el = document.getElementById('ppQr');
|
||||
if (!el) return;
|
||||
var link = el.getAttribute('data-link');
|
||||
if (!link || typeof qrcode !== 'function') return;
|
||||
try {
|
||||
var q = qrcode(0, 'M');
|
||||
q.addData(link);
|
||||
q.make();
|
||||
el.innerHTML = q.createSvgTag({ cellSize: 4, margin: 2, scalable: true });
|
||||
} catch (e) { /* leave the box empty rather than break the page */ }
|
||||
}
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', draw);
|
||||
} else {
|
||||
draw();
|
||||
}
|
||||
})();
|
||||
+2
-2
@@ -157,11 +157,11 @@ function render(rec) {
|
||||
(bullets ? '<ul>' + bullets + '</ul>' : '') +
|
||||
'<div class="cta"><p>' + esc(c.closing || 'Take a look and see what you think.') + '</p>' +
|
||||
'<a class="btn btn-primary" href="' + esc(link) + '">See how it works →</a>' +
|
||||
'<div class="qr" id="ppQr"></div><div class="micro" style="color:var(--muted)">or scan · ' + esc(link.replace(/^https:\/\//, '')) + '</div></div>' +
|
||||
'<div class="qr" id="ppQr" data-link="' + esc(link) + '"></div><div class="micro" style="color:var(--muted)">or scan · ' + esc(link.replace(/^https:\/\//, '')) + '</div></div>' +
|
||||
'<div class="foot">Independent team resource shared by an individual member. Participation takes real effort and involves cryptocurrency risk, including risk of total loss. No income is guaranteed. ' +
|
||||
'<a href="/disclaimer" style="color:var(--teal)">Disclaimers</a> · <a href="/contract" style="color:var(--teal)">How the contract works</a></div>' +
|
||||
'</main>' +
|
||||
'<script src="/qrlib.js"></script><script>(function(){try{var q=qrcode(0,"M");q.addData(' + JSON.stringify(link) + ');q.make();document.getElementById("ppQr").innerHTML=q.createSvgTag({cellSize:4,margin:2,scalable:true});}catch(e){}})();</script>' +
|
||||
'<script src="/qrlib.js"></script><script src="/page-qr.js"></script>' +
|
||||
'<script src="/chat.js" defer></script><script src="/translate.js" defer></script>' +
|
||||
'</body></html>';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user