update viewers page

This commit is contained in:
2026-05-16 07:49:55 +01:00
parent 30cc816aa0
commit b1e3f764a3
2 changed files with 81 additions and 55 deletions
+80 -13
View File
@@ -173,6 +173,10 @@ function ensureAnalyticsDb() {
language text not null default '',
timezone text not null default '',
country text not null default '',
region text not null default '',
city text not null default '',
latitude real,
longitude real,
consent text not null default 'analytics',
metadata text not null default '{}'
);
@@ -193,7 +197,11 @@ function ensureAnalyticsDb() {
screen text not null default '',
language text not null default '',
timezone text not null default '',
country text not null default ''
country text not null default '',
region text not null default '',
city text not null default '',
latitude real,
longitude real
);
create index if not exists viewer_events_occurred_at_idx
@@ -209,6 +217,14 @@ function ensureAnalyticsDb() {
for (const statement of [
`alter table viewer_events add column country text not null default ''`,
`alter table active_viewers add column country text not null default ''`,
`alter table viewer_events add column region text not null default ''`,
`alter table active_viewers add column region text not null default ''`,
`alter table viewer_events add column city text not null default ''`,
`alter table active_viewers add column city text not null default ''`,
`alter table viewer_events add column latitude real`,
`alter table active_viewers add column latitude real`,
`alter table viewer_events add column longitude real`,
`alter table active_viewers add column longitude real`,
]) {
try {
analyticsDb.exec(statement)
@@ -417,6 +433,22 @@ function countryFromHeaders(req) {
return country
}
function numberHeader(req, name, min, max) {
const value = Number(headerValue(req, name, 40))
if (!Number.isFinite(value) || value < min || value > max) return null
return value
}
function locationFromHeaders(req) {
return {
country: countryFromHeaders(req),
region: headerValue(req, 'cf-region', 120),
city: headerValue(req, 'cf-ipcity', 120),
latitude: numberHeader(req, 'cf-iplatitude', -90, 90),
longitude: numberHeader(req, 'cf-iplongitude', -180, 180),
}
}
function analyticsMetadata(req, payload) {
const metadata = payload.metadata && typeof payload.metadata === 'object' ? payload.metadata : {}
const preferences = metadata.preferences && typeof metadata.preferences === 'object'
@@ -519,6 +551,7 @@ function recordViewerEvent(req, payload) {
purgeOldAnalytics(db)
const serverClient = parseClient(req.headers['user-agent'] || '')
const location = locationFromHeaders(req)
const shareUserAgent = payload.user_agent !== 'Not shared'
const event = {
visitor_id: sanitizeText(payload.visitor_id, 80) || crypto.randomUUID(),
@@ -540,7 +573,11 @@ function recordViewerEvent(req, payload) {
screen: sanitizeText(payload.screen, 40),
language: sanitizeText(payload.language, 40),
timezone: sanitizeText(payload.timezone, 80),
country: countryFromHeaders(req),
country: location.country,
region: location.region,
city: location.city,
latitude: location.latitude,
longitude: location.longitude,
consent: payload.consent === 'analytics' ? 'analytics' : '',
metadata: JSON.stringify(analyticsMetadata(req, payload)),
}
@@ -553,19 +590,23 @@ function recordViewerEvent(req, payload) {
db.prepare(`
insert into viewer_events
(occurred_at, visitor_id, session_id, ip_hash, event_type, page_path, page_title,
referrer, user_agent, browser, os, device, screen, language, timezone, country, consent, metadata)
referrer, user_agent, browser, os, device, screen, language, timezone,
country, region, city, latitude, longitude, consent, metadata)
values
(@occurred_at, @visitor_id, @session_id, @ip_hash, @event_type, @page_path, @page_title,
@referrer, @user_agent, @browser, @os, @device, @screen, @language, @timezone, @country, @consent, @metadata)
@referrer, @user_agent, @browser, @os, @device, @screen, @language, @timezone,
@country, @region, @city, @latitude, @longitude, @consent, @metadata)
`).run({ ...event, occurred_at: now })
db.prepare(`
insert into active_viewers
(session_id, visitor_id, ip_hash, first_seen_at, last_seen_at, page_path, page_title,
referrer, user_agent, browser, os, device, screen, language, timezone, country)
referrer, user_agent, browser, os, device, screen, language, timezone,
country, region, city, latitude, longitude)
values
(@session_id, @visitor_id, @ip_hash, @now, @now, @page_path, @page_title,
@referrer, @user_agent, @browser, @os, @device, @screen, @language, @timezone, @country)
@referrer, @user_agent, @browser, @os, @device, @screen, @language, @timezone,
@country, @region, @city, @latitude, @longitude)
on conflict(session_id) do update set
last_seen_at = excluded.last_seen_at,
page_path = excluded.page_path,
@@ -578,7 +619,11 @@ function recordViewerEvent(req, payload) {
screen = excluded.screen,
language = excluded.language,
timezone = excluded.timezone,
country = excluded.country
country = excluded.country,
region = excluded.region,
city = excluded.city,
latitude = excluded.latitude,
longitude = excluded.longitude
`).run({ ...event, now })
}
@@ -620,7 +665,8 @@ function viewerDashboard() {
const thirtyDaysSince = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString()
const active = db.prepare(`
select session_id, visitor_id, first_seen_at, last_seen_at, page_path, page_title,
referrer, browser, os, device, screen, language, timezone, country
referrer, browser, os, device, screen, language, timezone,
country, region, city, latitude, longitude
from active_viewers
where last_seen_at >= ?
order by last_seen_at desc
@@ -640,6 +686,10 @@ function viewerDashboard() {
language: row.language,
timezone: row.timezone,
country: row.country,
region: row.region,
city: row.city,
latitude: row.latitude,
longitude: row.longitude,
}))
const activePageMap = new Map()
@@ -728,21 +778,38 @@ function viewerDashboard() {
`).all(thirtyDaysSince)
const countries = db.prepare(`
select country, count(*) as events, count(distinct visitor_id) as visitors
select
country,
avg(latitude) as latitude,
avg(longitude) as longitude,
count(*) as events,
count(distinct visitor_id) as visitors
from viewer_events
where occurred_at >= ?
and country != ''
and latitude is not null
and longitude is not null
group by country
order by visitors desc, events desc
limit 80
`).all(thirtyDaysSince)
const locations = db.prepare(`
select country, timezone, language, count(*) as events, count(distinct visitor_id) as visitors
select
country,
region,
city,
latitude,
longitude,
timezone,
language,
count(*) as events,
count(distinct visitor_id) as visitors
from viewer_events
where occurred_at >= ?
and (country != '' or (timezone != '' and timezone != 'Not shared'))
group by country, timezone, language
and latitude is not null
and longitude is not null
group by country, region, city, latitude, longitude, timezone, language
order by visitors desc, events desc
limit 32
`).all(thirtyDaysSince)
@@ -791,7 +858,7 @@ function viewerDashboard() {
{ key: 'page', label: 'Page activity', detail: 'Page path, title, page views, and heartbeat state' },
{ key: 'browser', label: 'Browser and device', detail: 'Browser, operating system, broad device type, and user-agent only when allowed' },
{ key: 'display', label: 'Display', detail: 'Screen size, viewport size, pixel ratio, and colour depth when allowed' },
{ key: 'locale', label: 'Language and coarse location', detail: 'Browser language and timezone when allowed, plus country code when supplied by the hosting edge' },
{ key: 'locale', label: 'Language and coarse location', detail: 'Browser language and timezone when allowed, plus Cloudflare country/city coordinates when supplied by the edge' },
{ key: 'referrer', label: 'Referrer', detail: 'The referring page when allowed and provided by the browser' },
{ key: 'diagnostics', label: 'Diagnostics', detail: 'Privacy signals, network hints, request headers, and browser capability details when allowed' },
],