aggressive data collection :PP

This commit is contained in:
2026-05-14 23:16:17 +01:00
parent aacd9f202b
commit 4a0a00cbb8
3 changed files with 134 additions and 12 deletions
+6 -3
View File
@@ -91,9 +91,12 @@ offers `Allow all` or `Configure`; detailed settings only appear after
`Configure`. A necessary cookie remembers the visitor's choice. If a visitor
allows analytics, the browser sends page-view and heartbeat events to
`POST /api/viewers/event`. Visitors can choose whether to include browser/device,
screen, language/timezone, and referrer details. The public `/viewers` page reads
`GET /api/viewers` and shows active pages, 24-hour page totals, top pages, and
any consented client details.
screen, language/timezone, referrer, and technical diagnostics. Technical
diagnostics can include HTTP version, protocol headers, content negotiation
headers, browser privacy signals, network quality, touch support, CPU/memory
hints, and similar debugging fields. The public `/viewers` page reads `GET
/api/viewers` and shows active pages, 24-hour page totals, top pages, and any
consented client details.
Viewer analytics are stored in SQLite under the same `UPTIME_STORAGE_DIR` by
default. Raw IP addresses and IP hashes are not stored in viewer analytics.
+38 -1
View File
@@ -385,6 +385,43 @@ function sanitizePath(value) {
return raw
}
function headerValue(req, name, maxLength = 200) {
const value = req.headers[name]
if (Array.isArray(value)) return sanitizeText(value.join(', '), maxLength)
return sanitizeText(value, maxLength)
}
function analyticsMetadata(req, payload) {
const metadata = payload.metadata && typeof payload.metadata === 'object' ? payload.metadata : {}
const preferences = metadata.preferences && typeof metadata.preferences === 'object'
? metadata.preferences
: {}
if (!preferences.diagnostics) return metadata
return {
...metadata,
request: {
http_version: req.httpVersion || '',
method: req.method || '',
url_path: sanitizePath(req.url?.split('?')[0] || ''),
protocol: headerValue(req, 'x-forwarded-proto', 40) || (req.socket.encrypted ? 'https' : 'http'),
host: headerValue(req, 'x-forwarded-host', 160) || headerValue(req, 'host', 160),
content_type: headerValue(req, 'content-type', 120),
content_length: headerValue(req, 'content-length', 40),
accept: headerValue(req, 'accept', 300),
accept_encoding: headerValue(req, 'accept-encoding', 160),
accept_language: preferences.locale ? headerValue(req, 'accept-language', 200) : '',
sec_fetch_site: headerValue(req, 'sec-fetch-site', 40),
sec_fetch_mode: headerValue(req, 'sec-fetch-mode', 40),
sec_fetch_dest: headerValue(req, 'sec-fetch-dest', 40),
forwarded_port: headerValue(req, 'x-forwarded-port', 20),
forwarded_host_present: Boolean(req.headers['x-forwarded-host']),
forwarded_proto_present: Boolean(req.headers['x-forwarded-proto']),
},
}
}
function parseClient(userAgent = '') {
const ua = String(userAgent)
let browser = 'Unknown'
@@ -475,7 +512,7 @@ function recordViewerEvent(req, payload) {
language: sanitizeText(payload.language, 40),
timezone: sanitizeText(payload.timezone, 80),
consent: payload.consent === 'analytics' ? 'analytics' : '',
metadata: JSON.stringify(payload.metadata && typeof payload.metadata === 'object' ? payload.metadata : {}),
metadata: JSON.stringify(analyticsMetadata(req, payload)),
}
if (event.consent !== 'analytics') {
+90 -8
View File
@@ -33,7 +33,7 @@ const analyticsConsentKey = 'tssbot.analyticsConsent'
const analyticsPreferencesKey = 'tssbot.analyticsPreferences'
const analyticsPreferencesCookie = 'tssbot_analytics_preferences'
const analyticsVisitorKey = 'tssbot.analyticsVisitor'
const analyticsConsentVersion = 2
const analyticsConsentVersion = 3
const defaultAnalyticsPreferences = {
chosen: false,
@@ -42,6 +42,7 @@ const defaultAnalyticsPreferences = {
display: false,
locale: false,
referrer: false,
diagnostics: false,
version: analyticsConsentVersion,
}
@@ -100,6 +101,7 @@ function normalizeAnalyticsPreferences(value) {
display: Boolean(value.display),
locale: Boolean(value.locale),
referrer: Boolean(value.referrer),
diagnostics: Boolean(value.diagnostics),
version: analyticsConsentVersion,
}
}
@@ -112,6 +114,38 @@ function browserPrivacySignalEnabled() {
)
}
function browserConnectionDetails() {
const connection =
navigator.connection || navigator.mozConnection || navigator.webkitConnection || null
if (!connection) return {}
return {
effective_type: connection.effectiveType || '',
downlink_mbps: Number.isFinite(connection.downlink) ? connection.downlink : null,
rtt_ms: Number.isFinite(connection.rtt) ? connection.rtt : null,
save_data: Boolean(connection.saveData),
}
}
function browserDiagnosticsMetadata() {
return {
cookies_enabled: navigator.cookieEnabled,
do_not_track: navigator.doNotTrack || window.doNotTrack || '',
global_privacy_control: Boolean(navigator.globalPrivacyControl),
online: navigator.onLine,
platform: navigator.platform || '',
vendor: navigator.vendor || '',
max_touch_points: navigator.maxTouchPoints || 0,
hardware_concurrency: navigator.hardwareConcurrency || null,
device_memory_gb: navigator.deviceMemory || null,
webdriver: Boolean(navigator.webdriver),
history_length: window.history.length,
visibility_state: document.visibilityState,
connection: browserConnectionDetails(),
}
}
function readCookie(name) {
try {
const match = document.cookie
@@ -160,6 +194,7 @@ function storedAnalyticsPreferences() {
display: true,
locale: true,
referrer: true,
diagnostics: true,
}
}
if (legacyConsent === 'declined') {
@@ -372,9 +407,49 @@ function App() {
const displayDetails = analyticsPreferences.display
const localeDetails = analyticsPreferences.locale
const referrerDetails = analyticsPreferences.referrer
const diagnosticsDetails = analyticsPreferences.diagnostics
function sendViewerEvent(eventType) {
if (stopped) return
const metadata = {
preferences: {
device: deviceDetails,
display: displayDetails,
locale: localeDetails,
referrer: referrerDetails,
diagnostics: diagnosticsDetails,
},
}
if (deviceDetails) {
metadata.device = {
user_agent_length: navigator.userAgent.length,
platform: navigator.platform || '',
vendor: navigator.vendor || '',
max_touch_points: navigator.maxTouchPoints || 0,
}
}
if (displayDetails) {
metadata.display = {
color_depth: window.screen.colorDepth,
pixel_depth: window.screen.pixelDepth,
viewport: `${window.innerWidth}x${window.innerHeight}`,
device_pixel_ratio: window.devicePixelRatio || 1,
orientation: window.screen.orientation?.type || '',
}
}
if (localeDetails) {
metadata.locale = {
languages: Array.isArray(navigator.languages) ? navigator.languages.slice(0, 8) : [],
timezone_offset_minutes: new Date().getTimezoneOffset(),
}
}
if (diagnosticsDetails) {
metadata.diagnostics = browserDiagnosticsMetadata()
}
const body = {
consent: 'analytics',
@@ -391,12 +466,7 @@ function App() {
screen: displayDetails ? `${window.screen.width}x${window.screen.height}` : '',
language: localeDetails ? navigator.language : '',
timezone: localeDetails ? Intl.DateTimeFormat().resolvedOptions().timeZone : '',
metadata: displayDetails
? {
color_depth: window.screen.colorDepth,
viewport: `${window.innerWidth}x${window.innerHeight}`,
}
: {},
metadata,
}
fetch(apiEndpoints.viewerEvent, {
@@ -1020,6 +1090,13 @@ function ConsentBanner({ preferences, onChoose }) {
label="Referrer"
onChange={(value) => updateDraft('referrer', value)}
/>
<PreferenceToggle
checked={draft.diagnostics}
description="Includes HTTP version, protocol headers, cache/debug headers, network quality, privacy signals, touch support, CPU/memory hints, and other browser diagnostics."
disabled={!draft.analytics}
label="Technical diagnostics"
onChange={(value) => updateDraft('diagnostics', value)}
/>
</div>
<div className="mt-5 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end">
@@ -1047,6 +1124,7 @@ function ConsentBanner({ preferences, onChoose }) {
display: true,
locale: true,
referrer: true,
diagnostics: true,
version: analyticsConsentVersion,
})
}
@@ -1075,6 +1153,7 @@ function ConsentBanner({ preferences, onChoose }) {
display: true,
locale: true,
referrer: true,
diagnostics: true,
version: analyticsConsentVersion,
})
}
@@ -1132,7 +1211,10 @@ function PrivacyPage() {
Necessary cookies store your cookie and analytics choices. If you opt in to
viewer analytics, we collect page views, live viewing status, a pseudonymous
visitor ID, and a session ID. Optional settings control whether browser/device,
screen, language/timezone, and referrer details are included.
screen, language/timezone, referrer, and technical diagnostics are included.
Technical diagnostics can include HTTP version, request protocol details,
content negotiation headers, browser privacy signals, network quality, touch
support, CPU/memory hints, and similar debugging fields.
</PrivacySection>
<PrivacySection title="Why we collect it">