openvibers/openvibe.network Commits

Chrome page-view beacon: POST /api/chrome/hit answers Cross-Origin-Resource-Policy: cross-origin. navigator.sendBeacon is a no-cors request, and helmet's same-origin policy made every page of every site log the (empty) answer as blocked; counting was never affected

OP OpenVibers committed 23 Sept 2026 · 34e76789b7 · 2 files, +15 −1


Changes

ddiff --git a/server/chrome/service.js b/server/chrome/service.js
iindex a2e899db..bb838e90 100644
---- a/server/chrome/service.js
++++ b/server/chrome/service.js
@@@ -182,7 +182,9 @@ function createChromeService(db, config, analytics, { privateKey = null, issuer
// browser-set Origin header (never the body) and must be one of ours or a registered tool domain.
const recent = new Map(); // ip → [count, windowStart]: 40/min is plenty for a person, useless for stuffing
router.post('/hit', (req, res) => {
-- res.set({ 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-store' });
++ // sendBeacon is a no-cors request: without CORP cross-origin the browser logs every beacon's
++ // (empty) answer as blocked on every page of every site (the count itself was never affected).
++ res.set({ 'Access-Control-Allow-Origin': '*', 'Cross-Origin-Resource-Policy': 'cross-origin', 'Cache-Control': 'no-store' });
try {
const ua = String(req.headers['user-agent'] || '');
let host = ''; try { host = new URL(String(req.headers.origin || '')).hostname.toLowerCase(); } catch { /* */ }
ddiff --git a/test/chrome.test.js b/test/chrome.test.js
iindex 696e26ab..c87d1937 100644
---- a/test/chrome.test.js
++++ b/test/chrome.test.js
@@@ -76,5 +76,17 @@ for (let i = 0; i < 6; i++) ins.run(1, 'tools', 'dns');
await createChromeService(new Database(':memory:'), cfg, null).refreshCopy();
assert.equal(liveHits, 0, 'Live is never asked for copy');
live.close(); ai.close();
++ // The page-view beacon (navigator.sendBeacon, a no-cors request) is readable cross-origin, so no
++ // site's console reports it as blocked.
++ {
++ const express = require('express');
++ const app = express(); app.use('/api/chrome', svc.router);
++ const srv = await new Promise(r => { const s = app.listen(0, '127.0.0.1', () => r(s)); });
++ const r = await fetch(`http://127.0.0.1:${srv.address().port}/api/chrome/hit`, { method: 'POST', headers: { origin: 'https://case.openvibe.tools', 'content-type': 'text/plain' }, body: '' });
++ assert.equal(r.status, 204);
++ assert.equal(r.headers.get('cross-origin-resource-policy'), 'cross-origin');
++ assert.equal(r.headers.get('access-control-allow-origin'), '*');
++ srv.close();
++ }
console.log('chrome service: all checks passed');
})().catch(e => { console.error(e); process.exit(1); });