Changeset 834bcc3 in Klonkt for src/services/StatsService.js
- Timestamp:
- 06/23/2026 06:14:27 PM (3 months ago)
- Branches:
- main
- Children:
- d774679
- Parents:
- bb42dfb
- File:
-
- 1 edited
-
src/services/StatsService.js (modified) (11 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/services/StatsService.js
rbb42dfb r834bcc3 1 // StatsService — cookie vrije statistieken (premium-module).1 // StatsService — cookie-free statistics (premium module). 2 2 // 3 // Tellers: posts.view_count, audio_tracks.play_count, en per dag/site het aantal4 // pageviews (stat_daily) + unieke bezoekers (stat_visitor_day).3 // Counters: posts.view_count, audio_tracks.play_count, and per day/site the number 4 // of pageviews (stat_daily) + unique visitors (stat_visitor_day). 5 5 // 6 // Uni eke bezoekers ZONDER cookie: een sha256 van IP+UA+dag-salt. De salt roteert7 // e lke dag en wordt nooit langer bewaard → je kunt iemand niet over dagen heen8 // volgen, het ruwe IP wordt niet opgeslagen. Geen persistente identifier, geen9 // toestemmingsbanner nodig (Plausible/Fathom-aanpak).6 // Unique visitors WITHOUT cookies: a sha256 of IP+UA+daily-salt. The salt rotates 7 // every day and is never stored longer → you cannot track someone across days, 8 // and the raw IP is never persisted. No persistent identifier, no consent 9 // banner required (Plausible/Fathom approach). 10 10 11 11 import crypto from 'node:crypto'; … … 17 17 } 18 18 19 // Da gelijks roterende salt (gecachet in proces, persistent in app_settings zodat20 // een herstart binnen dezelfde dag dezelfde salt houdt).19 // Daily rotating salt (cached in process, persisted in app_settings so that 20 // a restart within the same day reuses the same salt). 21 21 let _salt = null, _saltDay = null; 22 22 function dailySalt() { … … 39 39 } 40 40 41 // D e eigenaar/beheerder niet meetellen — anders inflate je je eigen cijfers.41 // Don't count the owner/admin — otherwise you inflate your own numbers. 42 42 function isOperator(req) { 43 43 const u = req && req.session && req.session.user; … … 45 45 } 46 46 47 // Bekende bots/crawlers + link-preview-fetchers + scripts overslaan, zodat ze de48 // weergaven/bezoeker-dagen niet opblazen. Geen UA = vrijwel altijd geautomatiseerd.47 // Skip known bots/crawlers + link-preview fetchers + scripts so they don't inflate 48 // view/visitor-day counts. Empty UA = almost always automated. 49 49 const BOT_RE = /bot|crawl|spider|slurp|mediapartners|bingpreview|facebookexternalhit|whatsapp|telegram|discord|twitter|linkedin|embedly|pinterest|redditbot|applebot|petalbot|yandex|baidu|duckduckbot|semrush|ahrefs|mj12|dotbot|uptimerobot|pingdom|statuscake|headless|lighthouse|gptbot|claude|ccbot|perplexity|bytespider|amazonbot|googleother|google-read-aloud|python-requests|scrapy|curl|wget|axios|node-fetch|go-http|java\/|okhttp|libwww|httpclient/i; 50 50 function isBot(req) { 51 51 const ua = (req && req.headers && req.headers['user-agent']) || ''; 52 if (!ua) return true; // legeUA = script/bot52 if (!ua) return true; // empty UA = script/bot 53 53 return BOT_RE.test(ua); 54 54 } 55 55 56 // Lazy prepares — tab ellen bestaan pas ná initializeDatabase(); dit module wordt57 // geïmporteerd vóór diecall.56 // Lazy prepares — tables only exist after initializeDatabase(); this module is 57 // imported before that call. 58 58 let _s = null; 59 59 function stmts() { … … 75 75 } 76 76 77 // Extern e referrer-host uit de Referer-header (pro-stats #5). Lege/eigen-site/78 // ongeldige referrers worden overgeslagen → alleen echte externe bronnen tellen.77 // External referrer host from the Referer header (pro stats #5). Empty/own-site/ 78 // invalid referrers are skipped → only genuine external sources are counted. 79 79 function recordReferrer(siteId, req) { 80 80 try { … … 84 84 if (!host) return; 85 85 const own = ((req.headers && req.headers.host) || '').replace(/^www\./, '').toLowerCase(); 86 if (host === own) return; // intern e navigatie telt niet als bron86 if (host === own) return; // internal navigation does not count as a source 87 87 stmts().bumpReferrer.run(siteId, host.slice(0, 120)); 88 } catch { /* geen geldige referrer-URL → overslaan*/ }88 } catch { /* not a valid referrer URL → skip */ } 89 89 } 90 90 … … 96 96 stmts().addVisitor.run(siteId, d, visitorHash(req)); 97 97 recordReferrer(siteId, req); 98 } catch { /* stat istieken mogen nooit een request breken*/ }98 } catch { /* stats must never break a request */ } 99 99 } 100 100 … … 112 112 } 113 113 114 // Instance- brede statistieken (solo = de site, hub = alle sites samen).114 // Instance-wide statistics (solo = the site, hub = all sites combined). 115 115 export function getStats(days = 14) { 116 116 days = [7, 14, 30, 90].includes(Number(days)) ? Number(days) : 14; … … 129 129 } 130 130 const totals = { 131 pageviews: series.reduce((s, r) => s + r.pageviews, 0), // la atste N dagen132 visitors: series.reduce((s, r) => s + r.visitors, 0), // s om van dag-uniques (cookieless kan niet anders)131 pageviews: series.reduce((s, r) => s + r.pageviews, 0), // last N days 132 visitors: series.reduce((s, r) => s + r.visitors, 0), // sum of daily uniques (cookieless has no alternative) 133 133 plays: db.prepare('SELECT COALESCE(SUM(play_count), 0) AS n FROM audio_tracks').get().n, 134 134 postViews: db.prepare('SELECT COALESCE(SUM(view_count), 0) AS n FROM posts').get().n, … … 142 142 ORDER BY play_count DESC LIMIT 5 143 143 `).all(); 144 // Top extern e bronnen (pro #5) — instance-breed geaggregeerdper host.144 // Top external sources (pro #5) — aggregated instance-wide per host. 145 145 let referrers = []; 146 146 try { … … 149 149 ).all(); 150 150 } catch { referrers = []; } 151 // All-time total en (cookieloze unieke bezoekers = som van dag-uniques).151 // All-time totals (cookieless unique visitors = sum of daily uniques). 152 152 const allTime = { 153 153 pageviews: db.prepare('SELECT COALESCE(SUM(pageviews),0) AS n FROM stat_daily').get().n,
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)