tacilytics 2.0.2 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +12 -1
- package/README.md +96 -3
- package/auth.js +788 -0
- package/cli.js +36 -0
- package/importers.js +223 -0
- package/package.json +10 -2
- package/public/account.js +570 -0
- package/public/app.js +229 -41
- package/public/assets/icons.svg +6 -0
- package/public/i18n.js +242 -0
- package/public/index.html +194 -37
- package/public/style.css +140 -0
- package/public/tacilytics.js +41 -1
- package/public/theme.js +14 -0
- package/report.js +300 -0
- package/server.js +572 -110
- package/tz.js +96 -0
- package/webhook.js +109 -0
package/.env.example
CHANGED
|
@@ -2,9 +2,15 @@
|
|
|
2
2
|
# Port (bound to 127.0.0.1; put a reverse proxy / TLS in front)
|
|
3
3
|
PORT=3920
|
|
4
4
|
|
|
5
|
-
# Dashboard passphrase (plaintext; keep .env at chmod 600)
|
|
5
|
+
# Dashboard passphrase (plaintext; keep .env at chmod 600).
|
|
6
|
+
# Only used until you create the first account in Settings → Security (then it stops working).
|
|
6
7
|
WEB_PASSWORD=change-me-to-a-strong-passphrase
|
|
7
8
|
|
|
9
|
+
# Public URL of the dashboard. Pins the passkey domain (recommended behind a reverse proxy).
|
|
10
|
+
# TACILYTICS_ORIGIN=https://analytics.example.com
|
|
11
|
+
# Reverse proxies in front (default 1). 0 = no proxy, the socket address is used for rate limits.
|
|
12
|
+
# TRUST_PROXY=1
|
|
13
|
+
|
|
8
14
|
# Secret for signing session tokens — long random string
|
|
9
15
|
# node -e "console.log(require('crypto').randomBytes(24).toString('hex'))"
|
|
10
16
|
SESSION_SECRET=change-me
|
|
@@ -21,3 +27,8 @@ API_KEY=change-me
|
|
|
21
27
|
|
|
22
28
|
# Data minimisation: auto-purge raw events older than N days (0 = keep forever)
|
|
23
29
|
DATA_RETENTION_DAYS=0
|
|
30
|
+
|
|
31
|
+
# Weekly report by e-mail (optional). smtps://…:465 or smtp://…:587 (STARTTLS)
|
|
32
|
+
# SMTP_URL=smtps://user:password@mail.example.com:465
|
|
33
|
+
# REPORT_FROM=Tacilytics <reports@example.com>
|
|
34
|
+
# REPORT_HOUR_UTC=6
|
package/README.md
CHANGED
|
@@ -111,9 +111,12 @@ Opt-in flags on the `<script>` tag:
|
|
|
111
111
|
<script defer data-domain="example.com"
|
|
112
112
|
data-honor-dnt <!-- skip tracking if DNT / Global Privacy Control is on -->
|
|
113
113
|
data-track-outbound <!-- auto-record clicks to other domains as events -->
|
|
114
|
+
data-track-downloads <!-- auto-record file downloads (pdf, zip, docx …) as "File Download"; or ="pdf,epub" -->
|
|
114
115
|
src="https://analytics.example.com/tacilytics.js"></script>
|
|
115
116
|
```
|
|
116
117
|
|
|
118
|
+
**Your own visits:** open your site once with `?tacilytics=off` (e.g. `https://example.com/?tacilytics=off`) and that browser is no longer counted on that site. `?tacilytics=on` undoes it. It is stored in the browser's localStorage, nothing is sent; repeat it on every browser and phone you use. Settings → Security has the links for all your sites.
|
|
119
|
+
|
|
117
120
|
## Mobile & desktop apps
|
|
118
121
|
|
|
119
122
|
Track your **native app in the same dashboard as your website**. Every row gets a platform
|
|
@@ -152,6 +155,52 @@ The server hashes the launch id with a random daily salt that is deleted when th
|
|
|
152
155
|
Without `sid`, the usual IP + user-agent daily hash is used. Region comes from the timezone or
|
|
153
156
|
device locale, never from IP geolocation.
|
|
154
157
|
|
|
158
|
+
## Import from Google Analytics or Plausible
|
|
159
|
+
|
|
160
|
+
**Import** (toolbar) brings your history along, so switching doesn't mean starting at zero.
|
|
161
|
+
|
|
162
|
+
- **Plausible:** Site settings → Imports & Exports → *Export data*. Upload the ZIP (or the CSVs in it).
|
|
163
|
+
- **Google Analytics 4:** open a report (*Pages and screens*, *Traffic acquisition*, *Demographic details*, *Tech*), add **Date** as a secondary dimension, *Share → Download file → CSV*. One file per report; English or German column names.
|
|
164
|
+
|
|
165
|
+
Imported numbers are daily totals per dimension (pages, sources, countries, browsers, OS, devices, events) and count **only for days before your own data starts** — never twice. They appear in the chart, the totals and the panels; while a filter is active they are left out (they can't be combined), and the dashboard says so. Every import can be removed again. No visitor-level data is imported or stored.
|
|
166
|
+
|
|
167
|
+
## Notes in the chart
|
|
168
|
+
|
|
169
|
+
Click **Notes** in the chart legend to mark a day — a newsletter, a release, a sale. A coral line and a diamond appear on that day, the note shows in the tooltip, and everyone with access to the site (including share links) sees it. Admins and owners add and remove notes. `GET /api/annotations?site=…` lists them; `/stats` returns the ones in its range as `annotations`.
|
|
170
|
+
|
|
171
|
+
## Time zone per site
|
|
172
|
+
|
|
173
|
+
Every site has its own time zone (any IANA name, e.g. `Europe/Berlin`; the browser's zone is preselected when you add a site, existing sites stay on `UTC`). Click the clock next to the site picker to change it. Days, the hourly view, date ranges, notes, the weekly report, the daily digest and the alert baseline all follow it, with daylight-saving days as 23 or 25 hours and offsets like +5:45 kept exact. Stored data is not touched, so switching back and forth is safe.
|
|
174
|
+
|
|
175
|
+
One limit: the anonymous visitor ID rotates at midnight **UTC** (that is what keeps it unlinkable across days). Outside UTC a local day therefore spans two IDs, and *unique visitors per day* are an approximation — pageviews, goals and revenue are exact.
|
|
176
|
+
|
|
177
|
+
`PATCH /api/sites/:id {"timezone":"Europe/Berlin"}` (admin) changes it via the API; unknown names are rejected with 400.
|
|
178
|
+
|
|
179
|
+
## Weekly report by e-mail
|
|
180
|
+
|
|
181
|
+
Settings → Security → **Weekly report**: enter an address and tick *every Monday*. Each person gets one mail per week for **the sites they can see** — visitors and pageviews vs. the week before, top pages, sources, goals and a link to the dashboard. *Send one now* sends a sample.
|
|
182
|
+
|
|
183
|
+
Needs mail settings in `.env`:
|
|
184
|
+
|
|
185
|
+
```env
|
|
186
|
+
SMTP_URL=smtps://user:password@mail.example.com:465 # or smtp://…:587 (STARTTLS)
|
|
187
|
+
REPORT_FROM=Tacilytics <reports@example.com>
|
|
188
|
+
REPORT_HOUR_UTC=6 # Monday, this hour (UTC)
|
|
189
|
+
TACILYTICS_ORIGIN=https://analytics.example.com # for the dashboard link in the mail
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Alerts
|
|
193
|
+
|
|
194
|
+
**Alerts** (toolbar) sends a message to a webhook — Discord, Slack, or anything that takes JSON — when:
|
|
195
|
+
|
|
196
|
+
- **Traffic spike:** about 3× the usual for this hour of the day,
|
|
197
|
+
- **Traffic drop:** under 20 % of usual (often: the tracker fell off the site),
|
|
198
|
+
- **Goal reached:** e.g. 10× `Purchase` within 1 hour, 24 hours or 7 days.
|
|
199
|
+
|
|
200
|
+
"Usual" is the median of the same hour over the last 14 days, so quiet nights don't look like incidents. Checked every 10 minutes, with a cooldown so one long spike is one message. **Test** sends a sample message. Admins and owners manage alerts.
|
|
201
|
+
|
|
202
|
+
Webhooks entered in the dashboard must be public `https://` addresses — no localhost, private ranges or redirects, so nobody on the team can use alerts to reach internal services. `DIGEST_WEBHOOK_URL` from `.env` is the default target (and may be internal); sites without own alerts keep the default spike/drop alerts there.
|
|
203
|
+
|
|
155
204
|
## Live visitors
|
|
156
205
|
|
|
157
206
|
Click **“● online now”** for a realtime view: a 30-minute pulse and a live feed of
|
|
@@ -189,6 +238,10 @@ tacilytics sites
|
|
|
189
238
|
tacilytics stats example.com 30d
|
|
190
239
|
tacilytics live example.com --watch
|
|
191
240
|
tacilytics add example.com
|
|
241
|
+
|
|
242
|
+
# on the server itself (reads the database, no API key)
|
|
243
|
+
tacilytics users
|
|
244
|
+
tacilytics reset-login <username>
|
|
192
245
|
```
|
|
193
246
|
|
|
194
247
|
Add `--json` to any command for machine-readable output — perfect for agents and scripts.
|
|
@@ -206,16 +259,54 @@ counts unique visitors *per day* and can’t follow the same person across days
|
|
|
206
259
|
devices. You get the 95 % that matters (how many, which pages, from where) with 0 %
|
|
207
260
|
cookie banner — the same approach as Plausible/Fathom.
|
|
208
261
|
|
|
262
|
+
## Light mode & German
|
|
263
|
+
|
|
264
|
+
Settings → Security → **Appearance**: *Theme* (System / Light / Dark) and *Language* (Automatic / English / Deutsch), saved per browser. *System* follows the device's light/dark setting live; *Automatic* picks German when the browser is German. Your data (paths, sources, event names) is never translated.
|
|
265
|
+
|
|
266
|
+
## Team, 2FA & passkeys
|
|
267
|
+
|
|
268
|
+
Out of the box the dashboard opens with one shared passphrase (`WEB_PASSWORD`). When you want more:
|
|
269
|
+
|
|
270
|
+
1. Sign in, open **Settings → Security** and create your **owner account**. From then on `WEB_PASSWORD` no longer opens anything, so nobody can walk around your 2FA with it.
|
|
271
|
+
2. **Settings → Team → Invite someone** creates a link that works **once, for 7 days**. Send it yourself; Tacilytics sends no e-mail.
|
|
272
|
+
|
|
273
|
+
| Role | Can |
|
|
274
|
+
|---|---|
|
|
275
|
+
| **Owner** | everything, including the team |
|
|
276
|
+
| **Admin** | add/remove sites, share links, funnels |
|
|
277
|
+
| **Viewer** | read only — optionally limited to **specific sites** |
|
|
278
|
+
|
|
279
|
+
Everyone can turn on, **optionally**:
|
|
280
|
+
|
|
281
|
+
- **Passkeys** (Face ID, Touch ID, Windows Hello, security keys). A passkey replaces password *and* code and cannot be phished.
|
|
282
|
+
- **One-time codes (TOTP)** from any authenticator app, with 10 single-use **recovery codes**. Codes cannot be reused.
|
|
283
|
+
|
|
284
|
+
**Forgot the password?** An owner clicks **Password link** next to the person in Settings → Team: a one-time link (24 h) to set a new one. Their 2FA stays on and all their sessions end.
|
|
285
|
+
|
|
286
|
+
**Activity log:** owners see every change in Settings → Activity — who added a site, invited someone, changed a role, created an API key, turned 2FA off … with time and address. Failed attempts and sign-ins are not logged, secrets never. Kept for a year (`AUDIT_RETENTION_DAYS`, minimum 30).
|
|
287
|
+
|
|
288
|
+
**Locked out?** On the server: `tacilytics users` lists accounts, `tacilytics reset-login <username>` prints a new password, turns 2FA off and ends that person's sessions. An owner can also reset a teammate's 2FA in Settings → Team.
|
|
289
|
+
|
|
290
|
+
**API keys per person:** Settings → Security → API keys creates a personal key (`tk_…`) for scripts, agents and the CLI. It acts as you and never more: it sees only your sites, is **read-only unless you untick it** (a viewer's key always is), and cannot manage the team or change security settings. Keys are stored as SHA-256, shown once, and can be revoked; removing a member revokes theirs.
|
|
291
|
+
|
|
292
|
+
The server-wide `API_KEY` from `.env` keeps owner rights for agents and the CLI on your own box.
|
|
293
|
+
|
|
209
294
|
## Configuration
|
|
210
295
|
|
|
211
296
|
| Env var | Default | Purpose |
|
|
212
297
|
|---|---|---|
|
|
213
298
|
| `PORT` | `3920` | Port (bound to 127.0.0.1) |
|
|
214
|
-
| `WEB_PASSWORD` | `tacilytics` | Dashboard passphrase |
|
|
215
|
-
| `
|
|
299
|
+
| `WEB_PASSWORD` | `tacilytics` | Dashboard passphrase — **only until the first account exists** |
|
|
300
|
+
| `TACILYTICS_ORIGIN` | request host | Public URL of the dashboard, e.g. `https://analytics.example.com`. Pins the passkey domain; recommended behind a proxy |
|
|
301
|
+
| `TRUST_PROXY` | `1` | Number of HTTP reverse proxies in front of Tacilytics that append `X-Forwarded-For` (e.g. Cloudflare + Caddy = `2`; a plain TCP forwarder like socat or `ssh -L` does not count). Rate limits **and visitor hashes** use the address the outermost trusted proxy saw, never the client-written first `X-Forwarded-For` entry. Too low: with two proxies and `1`, every visitor looks like the Cloudflare address and unique visitors collapse. Too high: clients can pick their own address and bypass the limits. Tacilytics binds to 127.0.0.1, so a proxy is always in front and `0` is almost always wrong (everyone becomes 127.0.0.1). A request without `X-Forwarded-For` logs a one-time warning |
|
|
216
302
|
| `INGEST_SALT` | fixed | Extra server-side secret mixed into the visitor hash (on top of the random daily salt) |
|
|
217
303
|
| `API_KEY` | — | Bearer key for the API / CLI |
|
|
218
304
|
| `DB_PATH` | `./data/analytics.db` | SQLite path |
|
|
305
|
+
| `DIGEST_WEBHOOK_URL` | — | Daily digest + default alert target (Discord/Slack-compatible). `off` disables it |
|
|
306
|
+
| `WATCH_MIN_VISITORS` | `10` | Spike/drop alerts stay silent below this many visitors per hour |
|
|
307
|
+
| `SMTP_URL` / `REPORT_FROM` | — | Mail server + sender for the weekly report |
|
|
308
|
+
| `REPORT_HOUR_UTC` | `6` | Monday hour (UTC) the weekly report goes out; it covers the last 7 full days in each site's time zone |
|
|
309
|
+
| `AUDIT_RETENTION_DAYS` | `365` | How long the team activity log is kept (min. 30) |
|
|
219
310
|
| `DATA_RETENTION_DAYS` | `0` | `0` = keep forever; `>0` purges raw events older than N days, daily (data minimisation) |
|
|
220
311
|
|
|
221
312
|
## Privacy & security
|
|
@@ -228,7 +319,9 @@ cookie banner — the same approach as Plausible/Fathom.
|
|
|
228
319
|
- **Respects DNT / Global Privacy Control** when you add `data-honor-dnt` to the tag.
|
|
229
320
|
- Engagement time adds **no new identifier** — it’s matched to the visitor’s own daily hash server-side.
|
|
230
321
|
- Custom event properties are capped (scalars only, size-limited) so they can’t become a PII dumping ground.
|
|
231
|
-
-
|
|
322
|
+
- Passwords hashed with scrypt; session tokens, invite links and recovery codes are stored only as SHA-256. Sessions live in the database (survive restarts, expire after 30 days).
|
|
323
|
+
- Timing-safe auth; login (per IP and per account), the 2FA step and the collect endpoint are rate-limited. Unknown usernames take as long as wrong passwords.
|
|
324
|
+
- Every per-site endpoint checks access: share links see exactly their site, viewers only the sites they were given.
|
|
232
325
|
- All visitor-supplied values are HTML-escaped before they reach the DOM.
|
|
233
326
|
- Run behind TLS; keep `.env` at `chmod 600`.
|
|
234
327
|
|