Files

50 lines
1.9 KiB
Markdown

# costco-watch
Polls a Costco tire product page on an interval and pushes an ntfy alert when
the item flips from out-of-stock to in-stock.
## The header gotcha
Costco's bot mitigation silently stalls requests that lack browser
fetch-metadata headers - TLS completes, then nothing comes back, no 403, just a
hang until timeout. Bisecting against the live site showed the gate is the
`Sec-Fetch-*` family: **any single one** of `Sec-Fetch-Site`, `Sec-Fetch-Mode`
or `Sec-Fetch-Dest` is enough to get a normal response, even with no
`User-Agent` at all. User-Agent, Accept and Accept-Language on their own make
no difference.
This service therefore sends a browser header set itself (see
`BROWSER_HEADERS` in `watch.js`). Two things that do **not** work as fixes:
- cloudflared cannot add headers - its `originRequest` block has no such
option, it only forwards what the client sent.
- Cloudflare Transform Rules cannot set `Sec-*` headers - they're on
Cloudflare's restricted list, so the edge can't supply the one header family
that matters.
`TARGET_URL` still points at the `costco-tire.alexzaw.dev` cloudflared ingress
(which sets `httpHostHeader: tires.costco.com`), but no Cloudflare-side
configuration is required.
## Config
| Env | Purpose |
|-----|---------|
| `TARGET_URL` | Product page to watch (required) |
| `ITEM_LABEL` | Friendly name used in notifications |
| `NTFY_URL` / `NTFY_TOPIC` | ntfy server and topic (topic required) |
| `NTFY_TOKEN` | Bearer token, if the topic is protected |
| `INTERVAL_MINUTES` | Poll interval, default 60 |
| `API_KEY` | Guards `/status` and `/check` |
## Endpoints
- `GET /health` - liveness
- `GET /status` - current stock state, last check, transition history
- `POST /check` - force a check now
## Etiquette
`tires.costco.com/robots.txt` allows `User-agent: *` on all paths, so a personal
low-frequency check is within its terms. Keep the interval at an hour or more.