In this note
find a source that actually respondskeep the browser away from the provideran hourly limit that survives failuresmeasurements, time and honest failure statesa tiny accessible windowextend the cache to 40 citiesreferencesfind a source that actually responds
A locker page mentioning sensors is a lead, not proof that its endpoint is working. The public InPost pages use an AJAX POST to a route shaped like /shipx-point-data/<id>/<locker>/air_index_level. Their script expects air_index_level and an air_sensors array. During this build, several Wrocław endpoints—including WRO103M—returned “Air sensors are not available.”
The useful alternative was Wrocław’s public InPost sensor map. The city states that it obtains these readings through cooperation with InPost. WRO252M at Kasprowicza 1 had current temperature, humidity, pressure, PM2.5 and PM10 readings. I matched both the street address and coordinates to InPost’s public locker record, rather than guessing from proximity.
keep the browser away from the provider
The public architecture has four parts: fixed public sensor feeds, a scheduled collector, validated JSON snapshots, and a browser popup. The collector runs on the server. The browser reads the city catalogue and the selected city snapshot under /api/environment/ on this site. The original /api/environment.json route remains compatible with the earlier Wrocław widget. Opening the cloud window cannot trigger another provider request. There is no map embed, external script, API key or third-party font.
The source is fixed in the collector. It accepts no user-provided URL, locker number or fetch target. That keeps this small endpoint from becoming an open proxy. Only the chosen station, known measurements and timestamps reach the public snapshot; raw provider responses and diagnostics stay out of it.
an hourly limit that survives failures
A private timestamp records the start of the last attempt before network I/O begins. A file lock prevents two collectors running together. Local scheduler checks do not contact the provider until at least 3,600 seconds have passed—even after a timeout, bad JSON or a restart. Network work has a time and size limit; there are no automatic upstream retries.
lock collector if now - last_attempt < 3600: stop save last_attempt before network I/O fetch the fixed public source validate one station and known fields atomically replace the public snapshot
Writing a temporary file and replacing the old snapshot atomically prevents readers receiving half a JSON document. The next-refresh timestamp includes the scheduler’s five-minute boundary. Browser countdowns use absolute time, so a sleeping tab catches up.
measurements, time and honest failure states
A missing number becomes null, never zero. Non-finite values and values outside broad physical ranges are rejected. Measurement time comes from the source; cache check time describes when this site fetched it. They are deliberately separate. Readings older than three hours are marked stale. A source failure retains the last known snapshot with a visible stale notice; a first failure displays an unavailable state.
The city’s Wrocław Air Index differs from InPost’s own category scale. The widget labels the city index explicitly instead of mixing the two. O₃ and NO₂ appear as dashes if the station feed does not supply them. These are indicative local sensor readings, not official regulatory measurements.
a tiny accessible window
The cloud is a real button with a descriptive label. A native <dialog> provides modal focus handling and Escape-to-close. Closing returns focus to the cloud button. Values use textContent, not provider HTML. Labels follow EN/PL and the window inherits the existing colour palette. A one-second countdown is visual rather than a constantly announcing screen-reader message.
The small foundation link is an ordinary link to Fundacja InPost, which supports employees and families in difficult circumstances and employee-led social initiatives. It is not a donation form or an endorsement of this widget.
extend the cache to 40 cities
The cloud now lets you choose among 40 large Polish cities, including all regional capitals. Each city has one verified locker; its values are local readings, not an average across the city. Wrocław keeps the municipal source described above. Other cities use the public InPost locker-page endpoint, discovered from the page’s own request URL and checked for real particulate measurements.
A compact catalogue at /api/environment/cities.json lists the supported cities. Each city has a separate read-only route, for example /api/environment/warszawa.json. The original /api/environment.json remains a Wrocław compatibility route. The browser chooses a fixed city ID from the catalogue; it cannot ask the collector to fetch an arbitrary URL. The city choice stays in this tab, without geolocation or tracking.
Direct InPost responses contain colon-separated sensor strings. For PM25:20.67:137.82, the raw measurement is the second field; the third field is a percentage, not another concentration. Parse only recognised names, check finite values and ranges, and leave absent measurements as null. Crucially, this endpoint does not publish a sensor timestamp. The widget says so and shows retrieval time separately; it never invents a measurement time or promises that a recent download proves a recent measurement.
The collector has one persisted hourly throttle per source, with no automatic retries after failures. At most four provider requests run sequentially per scheduler check, with a two-second gap, and a persisted budget limits all sources together to 40 attempts per rolling hour. HTTP 429 pauses that provider for at least six hours and honours longer Retry-After values. HTTP 401/403 suspends that provider until manual review; timeouts and server errors trigger a shared hourly pause. A lightweight scheduler checks every five minutes, so there are 80% fewer local checks than before; each source is still fetched no more than once per rolling hour. Atomic snapshots are served directly as static files. The browser loads the catalogue only when the cloud opens, reuses snapshots while switching cities, and pauses its countdown when the tab is hidden.