Request limits
This page answers risk #2 and risk #4. The account for tests is a real account
with paid memberships. Thus Gopher limits itself, and the limits are in one file
(lib/throttle.ts). Each request to Patreon goes through this file.
Four independent limits
-
A gap between requests. The gap is a minimum of 900 ms and a random quantity of up to 600 ms. The random quantity is necessary. A constant period is a signal that a machine, and not a person, sends the requests.
-
Backoff after HTTP 429. Gopher obeys the
Retry-Afterheader. Without that header, it doubles its own wait time, to a maximum of 120 seconds. If the wait is more than 15 seconds, the worker does not wait. It tells the panel, and the panel shows a counter. -
A budget for each time window. Gopher permits a maximum of 30 requests in 5 minutes. It refuses the requests above that number. Limits 1 and 2 control code that is correct. The budget controls code that is not correct. A loop in the panel is a fault that we can release, and the budget prevents such a fault from becoming a ban.
-
A limit on automatic requests. A request that the user did not ask for has the
autoflag. The service worker refuses a second automatic request for the same subject in 10 minutes. A request from the Refresh button has noautoflag, and the worker never refuses it.claimAutoAttempt()writes the time before the request, thus a stop in the middle of a request cannot permit an immediate second one. But a failure can happen before any request goes out: nopatreon.comtab, or a tab whose runner does not answer. Such a failure costs Patreon nothing, thusreleaseAutoAttempt()gives the claim back. Without this, a user who installs Gopher with no tab open waits 10 minutes for each attempt, and each attempt fails for the same reason. A failure aftercallRunner()keeps its claim, because it did use a request.
Three more rules decrease the number of requests
- The worker sends one request for the same data. Two panels can be open at the same time. The worker keeps a list of the requests in operation. A second request for the same data gets the reply of the first request.
- One page has an end. If a page contains no posts but has a cursor for a next page, Gopher stops. Without this rule the list does not increase, the panel asks for the next page again, and the sequence does not stop.
- The runner does nothing automatically. It waits for a message. The extension adds no requests when the user reads Patreon.
Why the limits are safe against a fault in the code
The service worker can stop at any time. Thus the limits are in browser.storage.session and
not in a variable. To read that storage, the code must wait. Two callers can therefore read the
same value. Then both reserve the same time and send their requests together. To prevent this,
the code makes the read and the write one operation that it cannot divide.
Test
tests/throttle.test.ts examines this file with 13 tests. Run it with npm run throttle:check.
It uses no browser and sends no request. Run it after each change to lib/throttle.ts, and
after each change to code that can start a request.
Measured behaviour in the harness on 2026-08-30: three panels in sequence made 1 request. Two panels at the same time also made 1 request, and 40 messages at the same time made 1 request too.