Commit Graph
4485 Commits
Author SHA1 Message Date
binwiederhier 6674f3b5bd Cancel a stuck usage load or sync on Close instead of waiting for it 2026-10-30 21:22:38 +00:00
binwiederhier 3fef91d342 Read the period end of a daily denial under the counter lock 2026-10-30 21:21:59 +00:00
binwiederhier 2b46b90df9 Check the row's age in the outer prune delete, so a row refreshed meanwhile survives 2026-10-30 21:21:40 +00:00
binwiederhier c3ecea55c4 Show the reset time and the sync delay on the account page, document shared daily limits
Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
2026-10-30 20:42:27 +00:00
binwiederhier c113e20677 Share daily limits through the usage table, drop the midnight reset and the stats queue
The server builds the usage store on the user database (the shared pool on
Postgres, the auth-file on SQLite) and starts the limiter before serving. A
visitor without a user writes its message count from a tenth of the cap on,
everyone else from the first message; anonymous emails get the daily cap the
replenish interval implies. The account API reports reset_at. The user stats
columns are no longer read or written.

Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
2026-10-30 20:41:36 +00:00
binwiederhier dad9cfa03b Sync daily counts with the usage table in a background worker
Every 5 s a node writes the unflushed counts of its promoted keys and reads back
the rows changed since the last round; at startup it loads the current rows
(waiting up to 30 s, then serving from memory while it keeps loading). A key is
promoted once it reaches its rule's threshold, or when another node's row for it
shows up. Rows not written for 25 h are pruned hourly.

Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
2026-10-30 20:33:09 +00:00
binwiederhier e074eaffa3 Add the usage store on SQLite and Postgres
One row per key: same period adds, a newer period replaces, and a leftover of an
older period is added to the stored one rather than taking the row back. Writes
are sorted by key and chunked; reads stream rows changed since a database time.

Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
2026-10-30 20:28:56 +00:00
binwiederhier b4669de064 Add the usage table in user schema migration 9 -> 10, zero the stats_* columns
Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
2026-10-30 20:27:29 +00:00
binwiederhier 59cee8430a Split daily counters from token buckets, roll each key over on its own 24h period
Daily counts (messages, emails, calls) now live in per-key counters that start
over lazily when the key's period ends; buckets keep their tokens. ResetAt
reports the end of the key's period. Seed, ResetUsage and Config.ResetTime are
gone, and an idle key is forgotten only once its buckets are full again.

Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
2026-10-30 20:23:47 +00:00
binwiederhier 9280bc1e14 Add per-key 24h periods from a hashed reset offset
Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
2026-10-30 20:15:19 +00:00
binwiederhier 5c2a1ddeef Release notes and install docs, prep v2.35.0
Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
v2.35.0
2026-10-30 13:24:49 +00:00
Philipp C. Heckel 3aa1b46bc1 Merge pull request #2129 from binwiederhier/email-cap
Do not cap emails without a tier, keep UnifiedPush rate visitors alive while a stream is open
2026-10-30 09:11:11 -04:00
binwiederhier e7cc914087 Keep a topic's rate visitor alive while any stream is open on it
Liveness no longer comes from keepalives: a topic keeps its rate visitor
while it has an open stream, and for a day after the last one closed or
the last subscribe. This removes the keepalive matching, taking a stale
topic back and its write re-check.

Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
2026-10-30 03:35:14 +00:00
binwiederhier f666779ee8 Check that a stream may still write before it takes a topic back
Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
2026-10-29 23:39:13 +00:00
binwiederhier 1883645c1a Keep a topic's rate visitor alive by the topic, for any stream with its key
A stream's keepalives refreshed only its own visitor, so a topic that held
the visitor of a newer stream or a poll from the same key lost it after a
day although a stream was still open. The topic now keeps when a
subscriber with its rate visitor's key was last seen, and a stream takes a
stale topic back.

Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
2026-10-29 22:54:19 +00:00
binwiederhier 1185578dc5 Do not turn off email without a tier when the refill is slower than a day
Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
2026-10-29 22:45:43 +00:00
binwiederhier be440b62ae Make the v2.35.0 release notes briefer and accurate
Tier to tier changes keep spend (no tier to a tier never did), only a tier's daily email limit is a cap, only the read over the size limit is refunded, and the #2119 bullet describes the fix.

Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
2026-10-29 21:10:24 +00:00
binwiederhier 4ed82ddd69 Do not cap the emails of visitors without a tier at a day's refill
Without a tier, the daily email limit is only derived from the refill, for
display. Enforcing it as a cap cut what the email bucket allowed: with a burst
of 5 and a refill every 288 minutes, 5 emails a day instead of about 10. Only
a tier's daily email limit is a cap now, as before.

Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
2026-10-29 21:10:24 +00:00
Philipp C. Heckel 8256b410c3 Merge pull request #2128 from binwiederhier/limit-release
Keep a stream as the topic's rate visitor when its subscriber polls
2026-10-29 16:44:30 -04:00
binwiederhier 63e25d667e Keep a same-key rate visitor on a poll even if it is stale
A stale one has only its tier from back then to lose, and a publish drops it anyway. Simpler without the check.

Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
2026-10-29 20:31:33 +00:00
binwiederhier 3cae50f18d Keep a stream as the topic's rate visitor when its subscriber polls
Since every request gets its own visitor, a poll from the subscriber of an
open stream replaced the stream's visitor as the topic's rate visitor. Only
the stream's keepalives keep a rate visitor alive, so a day later UnifiedPush
publishes failed with 50701 while the stream was still open. A poll from the
same key now keeps the live rate visitor (and keeps it alive) instead.

Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
2026-10-29 19:46:13 +00:00
binwiederhier d397e9d075 Put security first in the v2.35.0 release notes and merge the bug fix sections
Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
2026-10-29 16:44:17 +00:00
Philipp C. Heckel b9561e8088 Merge pull request #2118 from binwiederhier/visitor-per-request
Build the visitor per request instead of sharing it
2026-10-29 12:23:48 -04:00
binwiederhier 7aa105bdec Show the day's remaining emails on the account again
emails_remaining was clamped to what the email bucket holds right now, so a fresh
Supporter saw 10 of 50. It is the daily limit minus today's emails again, as in
v2.34.0 and like messages_remaining; the bucket still refuses a burst past it.

Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
2026-10-29 16:10:36 +00:00
binwiederhier 1e54713e29 Credit the reporter of the Matrix push key fix 2026-10-29 13:49:36 +00:00
binwiederhier eb9a12d734 Name the visitor's shared environment field env instead of embedding it
Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
2026-10-29 13:46:56 +00:00
binwiederhier 647698c607 Keep the visitor's subscription methods in visitor.go
Only the gauge lives in visitor_subscriptions.go; the tests follow their code.

Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
2026-10-29 13:24:57 +00:00
binwiederhier 31840bef1a Move the subscription gauge into visitor_subscriptions.go and test it
Covers the limit, release on stream close without leaving keys behind,
concurrent acquires, sharing per key, and the end of the Firebase penalty.

Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
2026-10-29 12:22:41 +00:00
binwiederhier 89cee1cd11 Keep a topic's rate visitor for as long as its subscriber is, not its key
Stale read the limiter key's last use, which every charged publish refreshes, so a topic kept
a vanished subscriber's limits (and its UnifiedPush subscription) for as long as anyone
published to it. The visitor now carries its own keepalive, set when it is built and by the
subscribe handlers' keepalive loops, like the shared visitor's seen was.

Also from the review: Prune returns and the manager logs the number of forgotten keys, the
unused visitorID wrapper goes, a stale comment and test name are updated, and the release
notes get the #1051 entry.

Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
2026-10-29 03:21:37 +00:00
binwiederhier d61081fe4e Name the shared visitor environment Server.visitors
Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
2026-10-29 03:04:24 +00:00
binwiederhier e2250f1671 Merge remote-tracking branch 'origin/main' into limit-2118 2026-10-29 02:43:07 +00:00
Philipp C. Heckel 08566a0f0d Merge pull request #2122 from binwiederhier/limit-fixes
Close three gaps in the limit rework and pin its behavior with tests
2026-10-28 22:41:29 -04:00
binwiederhier 9693d393c8 Merge branch 'limit-fixes' into visitor-per-request
The three fixes apply as they are (the email guard without the visitor mutex, which this
branch no longer has). Of the new tests, the two that pin the shared visitor's SetUser flush
and the expunge of visitor objects have no counterpart here; the rest compare visitor keys
where they compared object identity.

Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
2026-10-29 02:30:47 +00:00
binwiederhier aa9b89ef7b Add release note for terabyte size units 2026-10-29 02:29:50 +00:00
Philipp C. Heckel 3d967b0db0 Merge pull request #2078 from cangen185-sudo/codex/fix-parse-size-terabytes
fix(util): accept terabyte size units without overflow
2026-10-28 22:29:39 -04:00
binwiederhier 62abb9b729 Close three gaps in the limit rework and pin its behavior with tests
A tier with a daily email limit of zero sends no emails (the limit service reads a daily
limit of zero as "no cap"), a tier edited in place flushes the visitor's pending user stats
before resetting its local counts like a tier ID change does, and a bucket refill with a
timestamp older than its last one neither charges tokens nor moves the refill point back.

The tests pin what the rework changed and what it must keep: shared keys count once in the
user stats, an expunged visitor continues its counts, the daily reset keeps the buckets,
zero bursts admit nothing, seeds apply once, prefix keying, tier changes in all directions,
concurrency on a shared key, and the account page's remaining values matching admissions.

Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
2026-10-29 02:28:25 +00:00
binwiederhier a6cd0e4d7e Build the shared visitor config and IP limits once
Visitors are built per request, so what they share (the keying and limit
config, the config-based limits of visitors without a tier, the limiter and
the subscription gauge) is built once in New. Keys are built with string
concatenation instead of fmt.Sprintf.
2026-10-29 02:06:26 +00:00
binwiederhier f90c97f3de Keep new visitors fresh and stop checking the user in the auth and account limits
A visitor's key is touched when the visitor is built, so a subscriber whose
request skipped the limiter (an exempt prefix on an existing topic) is not
dropped as a stale rate visitor. The auth and account limits are per key and
ignore the user, as on main.
2026-10-29 01:55:33 +00:00
Philipp C. Heckel 1e2530e102 Merge pull request #2111 from binwiederhier/dependabot/npm_and_yarn/web/serialize-javascript-7.1.4
Bump serialize-javascript from 7.1.2 to 7.1.4 in /web
2026-10-28 21:54:12 -04:00
Philipp C. Heckel eecfbf645c Merge pull request #2082 from binwiederhier/dependabot/npm_and_yarn/web/vitest-5.0.4
Bump vitest from 5.0.3 to 5.0.4 in /web
2026-10-28 21:49:24 -04:00
Philipp C. Heckel e6f31170fa Merge pull request #2047 from binwiederhier/dependabot/npm_and_yarn/web/react-i18next-17.0.16
Bump react-i18next from 17.0.15 to 17.0.16 in /web
2026-10-28 21:49:15 -04:00
Philipp C. Heckel 0d2cd0388d Merge pull request #2083 from binwiederhier/dependabot/npm_and_yarn/web/i18next-26.4.4
Bump i18next from 26.4.2 to 26.4.4 in /web
2026-10-28 21:49:05 -04:00
Philipp C. Heckel 078aa9189d Merge pull request #2112 from binwiederhier/dependabot/npm_and_yarn/web/fast-uri-3.1.9
Bump fast-uri from 3.1.8 to 3.1.9 in /web
2026-10-28 21:48:55 -04:00
Philipp C. Heckel 8b54920a2f Merge pull request #2048 from binwiederhier/dependabot/github_actions/all-2f4c4b3894
Bump actions/setup-node from 7.0.0 to 7.1.0 in the all group across 1 directory
2026-10-28 21:48:19 -04:00
Philipp C. Heckel 9e44f3a4c0 Merge pull request #2114 from binwiederhier/dependabot/go_modules/github.com/gabriel-vasile/mimetype-1.4.16
Bump github.com/gabriel-vasile/mimetype from 1.4.15 to 1.4.16
2026-10-28 21:48:15 -04:00
Philipp C. Heckel 88335ea8ec Merge pull request #2115 from binwiederhier/dependabot/go_modules/google.golang.org/api-0.306.0
Bump google.golang.org/api from 0.301.0 to 0.306.0
2026-10-28 21:48:12 -04:00
binwiederhier 10da6ad425 Merge remote-tracking branch 'origin/main' into visitor-per-request
# Conflicts:
#	server/visitor.go
#	server/visitor_test.go
2026-10-29 01:41:04 +00:00
binwiederhier abd5ac3bb9 Add regression tests for the auth and account limiters under concurrent logins 2026-10-29 01:37:23 +00:00
Philipp C. Heckel f5cea2919a Merge pull request #2119 from binwiederhier/auth-limiter-race
Do not read the shared visitor's user in the auth and account limiters
2026-10-28 21:35:08 -04:00
binwiederhier fefe5e0c19 Do not read the shared visitor's user in the auth and account limiters
The callers only use these for anonymous requests, and the user on an IP-keyed visitor can be
set by another request from the same IP (#1051).

Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
2026-10-29 01:29:22 +00:00