[web_server] Show a hint when the hosted web interface cannot be downloaded

This commit is contained in:
J. Nick Koston
2026-08-19 12:51:51 -05:00
parent 470226ca03
commit 88d6ded5f3
2 changed files with 43 additions and 2 deletions
+15 -2
View File
@@ -2,6 +2,7 @@ from __future__ import annotations
import base64
import gzip
from html import escape as html_escape
import logging
import re
@@ -344,8 +345,20 @@ def build_index_html(config) -> str:
if js_include:
html += "<script type=module src=/0.js></script>"
html += "<esp-app></esp-app>"
if config[CONF_JS_URL]:
html += f'<script src="{config[CONF_JS_URL]}"></script>'
if js_url := config[CONF_JS_URL]:
# The interface is downloaded from the internet. Show a hint instead of a blank
# page when the browser cannot reach it, which is common in WiFi AP mode.
hint = (
f"Could not load the web interface from {js_url}. This browser needs internet "
"access to download it. To serve it from the device instead (for example in "
"WiFi access point mode), set local: true under web_server: in the YAML "
"configuration and install the firmware again."
)
# Escape for a JS single quoted string inside a double quoted HTML attribute.
hint_js = html_escape(
hint.replace("\\", "\\\\").replace("'", "\\'"), quote=True
)
html += f'<script src="{js_url}" onerror="document.body.innerText=\'{hint_js}\'"></script>'
html += "</body></html>"
return html
@@ -0,0 +1,28 @@
"""Tests for the web_server component index page builder."""
from esphome.components.web_server import build_index_html
from esphome.const import CONF_CSS_URL, CONF_JS_URL
def test_build_index_html_has_offline_hint() -> None:
"""The hosted script tag shows a hint when the browser cannot download it."""
html = build_index_html(
{CONF_JS_URL: "https://oi.esphome.io/v2/www.js", CONF_CSS_URL: ""}
)
assert '<script src="https://oi.esphome.io/v2/www.js" onerror="' in html
assert "local: true" in html
assert "https://oi.esphome.io/v2/www.js. This browser needs internet" in html
def test_build_index_html_escapes_hint() -> None:
"""Quotes in the script URL cannot break out of the onerror attribute."""
html = build_index_html({CONF_JS_URL: "http://x/a'b\"c.js", CONF_CSS_URL: ""})
assert "onerror=\"document.body.innerText='" in html
assert "a\\&#x27;b&quot;c.js" in html
def test_build_index_html_without_js_url() -> None:
"""No hosted script and no hint when js_url is empty."""
html = build_index_html({CONF_JS_URL: "", CONF_CSS_URL: ""})
assert "onerror" not in html
assert "<script" not in html