Releases: GabrielGoldsteinAnidea/HA-Websocket-Stripper
Release list
0.2.3 — auto-entities regex/template/group resolution, XFF chain, self-healing kiosks
Four reported issues, all fixed. Thanks to @lieblinger (#10), @Ltek (#4), @dimatx / @genik70 (#9) and @davidcoulson (#7) — every fix here came from a specific report, and several were only findable because people attached real YAML and real logs.
Local add-on reminder: code is baked into the image at build time, so this needs a Rebuild, not a Restart.
auto-entities: globs and regexes now work on every filter key (#10)
Only * globs were ever understood. A /regex/ was escaped as literal text, so
filter:
include:
- entity_id: "/^sensor\.pv_.*_power$/"compiled to ^/\^sensor\\.pv_.*_power\$/$ — a pattern matching no entity id that has ever existed. Those entities were stripped and the cards showed "unavailable".
The matcher is now a port of auto-entities' own (src/match.ts), with its exact semantics:
| Pattern | Behaviour |
|---|---|
/^sensor\.pv_.*_power$/ |
regex, not auto-anchored — you supply ^ / $ |
sensor.pv_* |
glob, anchored |
sensor.foo |
exact equality |
Critically it now applies to every filter key — domain, area, label, device, integration, name — not just entity_id, which is what upstream does. If you mirrored patterns into always_forward as a workaround, you can drop them.
Cards whose entities the proxy could not see (#4)
Three separate causes, all of which presented identically as "this card works on :8123 but not through the proxy":
- HA's selector object form. The visual editor stores values as
{ custom: "input_boolean.bypass_*", active_choice: "custom" }. That was handled forarea/label/device/integrationbut not forentity_id/domain, where it stringified to"[object Object]"and matched nothing. filter: template:cards. Their entity list only exists after HA renders the Jinja, so a structural walk could never see it. The proxy now renders those templates over its existing control connection and takes the real entity ids from the output. A template that errors or times out contributes nothing and no longer affects the rest of the dashboard.- Group members. A card naming only a group — or expanding one client-side, like
enhanced-shutter-card'sshow_group_members: true— left every member stripped, because the members appear nowhere in the dashboard config. Any allowlisted entity now contributes itsentity_idattribute members, transitively. The auto-entitiesgroup:andname:filter keys are supported for the same reason.
400 Bad Request behind another reverse proxy (#9)
If you run Caddy / nginx / Traefik in front of this add-on, every request through it returned 400 while :8123 worked fine.
The handler that normalizes IPv4-mapped IPv6 used setHeader, replacing the entire forwarded chain with our immediate peer. Since http-proxy appends our hop to all three forwarded headers, HA received:
X-Forwarded-For: <caddy> (1 entry — flattened by us)
X-Forwarded-Proto: https,http (2 entries)
and HA's forwarded middleware raises HTTPBadRequest on len(forwarded_proto) not in (1, len(forwarded_for)). Entries are now normalized in place, so the counts stay in step and the real client IP survives the upstream hop — which also means trusted_networks sees the browser rather than your proxy. The same normalization now applies to websocket upgrades, which never fired the HTTP-only hook.
Open dashboards pick up new entities by themselves (#7)
subscribe_entities is sent once per connection and HA cannot amend a live subscription, so a rebuild only ever affected new connections — an already-open kiosk kept its original entity list until someone physically reloaded the tab.
When a rebuild adds entities, affected connections are now dropped; the frontend treats that as an ordinary disconnect and reconnects against the current allowlist. Edit a dashboard and your wall panels catch up on their own. Removals deliberately don't churn open connections — carrying a few entities you no longer need is harmless.
Notes
- Your allowlist may grow slightly after updating. That is the point: regex,
name, and group-member entities that previously resolved to nothing are now included. - Still unsupported, and treated as matching nothing:
not,and,or,floor,device_manufacturer,device_model,last_changed. List those entities inalways_forwardand open an issue if you rely on one. - Tests: 44 → 75.
Full changelog: v0.2.2...v0.2.3
0.2.2 — survive an HA restart; never forward an empty allowlist
Update strongly recommended. This release fixes a bug that inverted the add-on's purpose on every fresh install.
Huge thanks to @maxi1134, who diagnosed both headline bugs on a live ~3,600-entity instance and sent the fix in #11.
An empty allowlist was being relayed as no filter
HA parses subscribe_entities as set(msg.get("entity_ids", [])) or None. An empty entity_ids is therefore not "subscribe to nothing" — the or None makes it no filter at all.
config.yaml shipped with dashboards defaulting to fridge-status / home-status / dashboard-deck. Those are my own dashboards. On anyone else's instance all three return config_not_found, the union came back empty, and the add-on then streamed every entity on the instance — the exact firehose it exists to prevent. On the reporting instance that hit Node's 2 GB heap limit every 30–130 seconds in a Supervisor restart loop. Because reinstalling an add-on resets options to their defaults, a fresh install landed straight in it.
dashboardsnow defaults to[]./api/websocketis refused whenever the allowlist is empty, with the reason named in the log. A second guard in the relay drops the connection rather than ever sending an emptyentity_ids.- A failed dashboard fetch now logs the dashboards that do exist, so
config_not_foundanswers its own question. - No dashboards configured no longer exits — that just moved the restart loop into the Supervisor.
⚠️ If you never set the dashboards option
You were silently running with no trimming at all. After updating, the add-on will refuse /api/websocket and say so in the log until you set dashboards to your own url_path values (Settings → Dashboards). That is the fix working, not a regression.
Surviving a Home Assistant restart
Restarting HA killed the add-on and left it crash-looping until started by hand. Three causes:
- Unhandled socket error on an in-flight ws upgrade. A raw upgrade socket arrives with no
'error'listener, andhttp-proxyonly attaches one after HA answers101. An HA restart resets every in-flight stream at once (camera, Assist), and a reset in that window reached Node as a fatal unhandled'error'. process.exit(2)when the first allowlist couldn't be built. The add-on and core restart together, so each restarted container died in ~300 ms until the Supervisor gave up. It now retries on the same backoff used for later drops.- The proxy now listens before HA is reachable, so it serves the moment core answers. Until the first allowlist exists,
/api/websocketgets a503and the frontend retries on its own.
A restarting HA also returns in stages, which used to leave the allowlist wrong:
- A rebuild after reconnect merges rather than replaces, so a half-loaded core can't shrink a good allowlist. Real dashboard/registry edits still replace, so removals still take effect.
buildAllow()bails when every dashboard fails (HA authenticating but not yet serving lovelace) so the caller retries. A single dashboard failing is still tolerated — that's a typo'durl_path.- The control connection has a handshake timeout, so a core that accepts TCP but never completes the handshake still triggers a reconnect.
Also
EADDRINUSE/EACCESon startup now exits naming theportoption instead of printing a raw stack (#6).auth_invalidno longer exits; it logs loudly and retries slowly.- Repeated failures are collapsed in the log instead of flooding hundreds of identical lines per second.
DOCS.mdno longer claims the allowlist is computed only at startup — live recompute has been in since 0.2.0.- Test suite grown to 44, covering an HA restart, a cold boot against a down HA, a half-started core, and the empty-allowlist guard.
Local add-on reminder: code is baked into the image at build time, so this needs a Rebuild, not a Restart.
Full changelog: v0.2.1...v0.2.2