From 6f6d1a2a7ae43747f3192195f95670507eb1fe1d Mon Sep 17 00:00:00 2001 From: mikecarper Date: Wed, 30 Sep 2026 19:20:12 -0700 Subject: [PATCH] Fix WebConfig bootstrap for legacy Android Chrome Physical LG Android 5.1.1 testing found Chrome 43 could decode gzip but failed on the bootstrap's arrow syntax. Use ES5 functions and Promises while preserving retries, version refresh, and optional abort handling. Keep Google Zopfli gzip compression at 1,000 iterations. Verified the flashed V4.3 through its WiFi link using the real Android browser: bootstrap and main page gzip responses completed, settings loaded at 910.525 MHz, and no JavaScript or transfer errors were reported. Add a loader syntax regression and document legacy browser support. --- docs/WiFi.md | 2 + scripts/generate_webconfig_html.py | 21 +++++----- src/helpers/esp32/WebConfigHtml.h | 64 +++++++++++++++--------------- test/test_webconfig_gzip.py | 7 ++++ 4 files changed, 52 insertions(+), 42 deletions(-) diff --git a/docs/WiFi.md b/docs/WiFi.md index ba4c1d5a..51ab773d 100644 --- a/docs/WiFi.md +++ b/docs/WiFi.md @@ -112,6 +112,8 @@ Browsers decompress the pages automatically over ordinary HTTP. WebConfig returns HTTP 406 when a client explicitly excludes gzip. Its large page closes the TCP connection gracefully after the final acknowledged bytes so browsers do not report a completed download as aborted. +The bootstrap uses ES5 syntax and Promise-based loading, including on Android +5.1.1 with Chrome 43; newer JavaScript syntax must not prevent the page loading. Most full-size ESP32 MQTT observer builds have the shared WebConfig portal. On a fresh device with no saved SSID: diff --git a/scripts/generate_webconfig_html.py b/scripts/generate_webconfig_html.py index 1feb56cd..b70bec3f 100644 --- a/scripts/generate_webconfig_html.py +++ b/scripts/generate_webconfig_html.py @@ -135,16 +135,19 @@ def main(): loader = ("MeshCore " "WebConfigLoading MeshCore WebConfig…" % version) + "\". Reload to retry.\"})}load()})()" % version) loader_bytes = compress_page(loader.encode("utf-8")) success_bytes = compress_page(b"SuccessSuccess") etag = hashlib.sha256(loader_bytes).hexdigest()[:16] diff --git a/src/helpers/esp32/WebConfigHtml.h b/src/helpers/esp32/WebConfigHtml.h index cdbae614..6d49c7d5 100644 --- a/src/helpers/esp32/WebConfigHtml.h +++ b/src/helpers/esp32/WebConfigHtml.h @@ -7,42 +7,40 @@ #include const uint32_t WEBCONFIG_HTML_GZ_LEN = 33969; -const char WEBCONFIG_HTML_ETAG[] = "\"96a1dfcd38a0ae5a\""; +const char WEBCONFIG_HTML_ETAG[] = "\"25ca2f61b991b105\""; const char WEBCONFIG_HTML_VERSION[] = "4d14ae4de3268605"; -const uint16_t WEBCONFIG_HTML_LOADER_LEN = 493; +const uint16_t WEBCONFIG_HTML_LOADER_LEN = 462; const uint16_t WEBCONFIG_HTML_SUCCESS_LEN = 65; const uint8_t WEBCONFIG_HTML_LOADER[] PROGMEM = { - 0x1f,0x8b,0x08,0x00,0x00,0x00,0x00,0x00,0x02,0xff,0xb2,0x51,0x4c,0x01,0xb4,0x49, - 0x1d,0xcb,0x91,0xc2,0x40,0xf4,0x57,0x54,0x0e,0x94,0x34,0x79,0x9c,0x6d,0x10,0x1b, - 0xa8,0x3d,0x6d,0xce,0x67,0x8d,0x68,0x8c,0x6a,0x85,0x44,0xb5,0x1a,0xe3,0xf8,0xef, - 0x2b,0x9c,0x53,0x5f,0x3a,0xe7,0xe7,0x35,0x9d,0xb5,0xc0,0x6a,0x6a,0x6c,0x9e,0x35, - 0x40,0x8a,0xe9,0x5a,0x61,0x00,0x92,0x1d,0x55,0xd3,0x83,0x5b,0x9b,0x53,0x0d,0xc8, - 0x13,0x03,0x7d,0xeb,0x91,0x98,0xf6,0x8e,0xc0,0x91,0x5c,0xeb,0x4d,0x49,0xb5,0x2c, - 0xe1,0xc4,0x68,0x98,0x5e,0x2b,0x13,0xe3,0x0c,0x19,0x65,0xa7,0x41,0x2b,0x0b,0x72, - 0xb9,0x96,0x67,0x64,0xc8,0x42,0xfe,0x19,0x42,0x5d,0x78,0x04,0xf6,0x17,0x56,0x85, - 0x77,0x95,0x39,0xce,0xe6,0x37,0x9e,0x6c,0xe5,0xcb,0xb3,0xfc,0x93,0x57,0xa5,0x71, - 0xc7,0xec,0x65,0x5c,0x52,0x83,0xb5,0xa6,0x4d,0xb3,0xa0,0xd1,0xb4,0x94,0x73,0x15, - 0xce,0x9c,0xe6,0x42,0xe6,0x17,0x16,0x88,0xb5,0x69,0xe5,0x91,0x0f,0x92,0x93,0x8b, - 0xd4,0x65,0xdb,0x49,0xd2,0x4a,0x29,0x3b,0x57,0x42,0x65,0x1c,0x94,0xa9,0x1b,0x8f, - 0xc5,0x85,0xf6,0x2e,0x10,0x2b,0x64,0x6f,0x5c,0xe9,0xfb,0xd9,0xbb,0x95,0x47,0x8a, - 0xe5,0x09,0xbd,0xb5,0x80,0x13,0x2d,0x8b,0x24,0x71,0xd0,0xb3,0x82,0x8b,0x09,0x49, - 0x9d,0x24,0x01,0xe8,0x97,0x69,0xc0,0x77,0xc4,0x87,0x56,0x7a,0xa6,0x86,0x94,0xe8, - 0x3d,0x5c,0x44,0x12,0x29,0xe1,0xd9,0x6d,0x51,0x94,0xaa,0x57,0x86,0x58,0x05,0xa4, - 0x6b,0xbe,0x36,0xef,0xcc,0x9b,0x13,0xb9,0x53,0x2e,0x77,0x14,0xec,0x94,0xb0,0xbd, - 0xb5,0x77,0xb0,0xb7,0xd8,0x5d,0x9b,0xe8,0x37,0x17,0xc1,0x1c,0x3b,0x65,0x8f,0xf4, - 0xec,0x46,0xb8,0x3a,0xba,0xb8,0x12,0xa9,0xa9,0x38,0xce,0xfc,0x3f,0xd1,0xde,0x96, - 0xc1,0x19,0xc1,0x69,0x6c,0x94,0x82,0x0d,0xc0,0xae,0xbd,0x81,0x14,0x75,0x41,0x4a, - 0xb9,0xb3,0x38,0x14,0x08,0xd4,0xa1,0x63,0xd6,0x6b,0x45,0xc6,0xbb,0x19,0x82,0x8d, - 0xa7,0xe3,0xe2,0x2a,0xea,0xb1,0x3f,0x88,0x8b,0xab,0xca,0xc4,0xea,0xf6,0xec,0x22, - 0x26,0x93,0xd0,0x16,0x14,0xde,0xae,0x12,0xd5,0xab,0x68,0x7c,0x72,0x1f,0x71,0xd3, - 0x76,0x58,0xfe,0x1b,0xfa,0xc6,0x04,0xe0,0x28,0xf3,0x47,0xeb,0xe3,0x64,0x77,0xb1, - 0x18,0x71,0x37,0x5e,0x0a,0xf1,0x4a,0x3a,0xd5,0xe8,0x7b,0xf6,0x01,0xd1,0x23,0x5f, - 0x23,0x54,0x2e,0x54,0x80,0xac,0x52,0xc6,0x42,0xb9,0x26,0xd2,0x08,0xb0,0xae,0x01, - 0x47,0x33,0xdf,0x82,0xe3,0x8f,0xf4,0x1e,0x0d,0x01,0x6f,0x1f,0x59,0xb4,0xf5,0x01, - 0xe2,0x22,0x82,0x8b,0xd9,0xed,0x32,0x32,0xbf,0xf7,0x0e,0x28,0xb9,0x3e,0x4d,0x71, - 0x87,0xbe,0xdf,0x4e,0xad,0x2c,0x30,0xf2,0x6c,0xb8,0xc0,0x03,0x60,0x8e,0xd8,0xda, - 0x18,0xc6,0x6b,0x33,0xf6,0x03,0x06,0xc7,0x10,0x80,0x40,0x78,0x36,0x5b,0x13,0xd9, - 0xfc,0x16,0x44,0xff,0x01,0x17,0x05,0x67,0x30,0xfa,0x02,0x00,0x00, + 0x1f,0x8b,0x08,0x00,0x00,0x00,0x00,0x00,0x02,0xff,0x75,0x52,0x47,0x77,0xdb,0x30, + 0x0c,0xfe,0x2b,0x6c,0xa6,0xe8,0xed,0xec,0x44,0xa2,0x3b,0xf4,0x7a,0xea,0xde,0x67, + 0x9a,0x82,0x2c,0xbe,0xd2,0x80,0x1e,0x04,0x59,0x59,0xfa,0xef,0x8d,0x57,0x77,0x70, + 0xc1,0xde,0x5f,0xf2,0x24,0x23,0x27,0x37,0x25,0xa8,0x42,0xe6,0x61,0x92,0xcc,0x41, + 0xac,0x72,0x85,0xe5,0x0a,0xc4,0xd4,0x92,0xf7,0x2f,0x36,0x36,0xb4,0x73,0x30,0x0b, + 0x0f,0x4d,0x49,0x2c,0xca,0x11,0x0a,0xa0,0x98,0x9d,0xc6,0x67,0x52,0x98,0x0c,0x16, + 0xde,0x41,0x7f,0xa5,0xf4,0x3c,0x7a,0xf1,0x36,0xf4,0x2b,0x67,0x03,0x98,0xf1,0xce, + 0x24,0x11,0x2f,0x01,0x26,0x6f,0xa0,0x2a,0x52,0x62,0x50,0xdf,0x60,0x9a,0x12,0xe6, + 0x7e,0x96,0x0c,0xd7,0x9e,0x64,0x4a,0xd9,0xcd,0xe4,0x35,0xd9,0xcc,0xe3,0x4c,0xfd, + 0x1b,0x77,0x50,0x40,0x08,0xbe,0x8c,0x93,0xca,0xb1,0x2f,0x65,0x12,0xe5,0x35,0x3a, + 0xf1,0x84,0x91,0xbe,0x5b,0x58,0x56,0x68,0x46,0xf1,0xd6,0xa4,0xc2,0x43,0x99,0x8d, + 0x3d,0x35,0x8d,0xc7,0x8c,0x9a,0xc1,0xf3,0x29,0xb1,0xa4,0x84,0xc2,0x14,0x02,0x70, + 0xcf,0x99,0xf4,0xe0,0x00,0xa1,0x51,0x69,0xa4,0x7b,0x62,0xdc,0xc1,0x41,0x05,0xf2, + 0xd9,0xcf,0x81,0x6a,0xf9,0xbd,0xb8,0x1b,0xd8,0x65,0x66,0xa4,0xdb,0xde,0xe5,0xe8, + 0x81,0x74,0x9c,0x83,0xb8,0x22,0xda,0x19,0xd6,0xfe,0xe9,0xc2,0x9c,0x64,0xe3,0x13, + 0x0b,0x27,0x19,0x1c,0x1f,0x9d,0x5d,0x9c,0x8d,0x4e,0x77,0x7a,0xee,0xe9,0x5d,0xe5, + 0x67,0x68,0xc3,0x95,0x1b,0xac,0x85,0xf6,0xea,0xae,0xd5,0x03,0x29,0x00,0x7f,0x15, + 0x66,0x7d,0xe7,0xf3,0x88,0x07,0xf4,0x5d,0x33,0x48,0xcd,0xa8,0x78,0x20,0x70,0x2d, + 0x91,0x8e,0x57,0xf6,0x4a,0xac,0xd4,0x95,0x31,0xe6,0x64,0x74,0xa9,0xef,0x02,0x39, + 0xbb,0x4c,0x1b,0x30,0xac,0x77,0x8b,0x37,0x49,0x58,0x87,0xd0,0x4a,0xc1,0xd4,0xa8, + 0x97,0xcc,0xc4,0xd1,0x8e,0xb0,0xc5,0x2a,0x07,0x56,0xb9,0xf5,0x01,0xb2,0x1d,0xfd, + 0x4f,0xeb,0x72,0xd5,0x5a,0xb4,0x0b,0x60,0x79,0xbb,0xb1,0xac,0xfa,0x96,0xc6,0x98, + 0x65,0xc9,0xcd,0x4c,0x71,0x46,0xae,0x9e,0x03,0xca,0x80,0x4a,0xc0,0x48,0xff,0xd2, + 0x1b,0xf6,0x02,0x51,0xf9,0x9b,0xc5,0x05,0xaa,0x60,0x79,0xa5,0x9f,0x7d,0xe0,0xf1, + 0x3e,0xdd,0x2e,0x26,0xc7,0xfa,0xb7,0x83,0x2f,0xb7,0xea,0x9d,0x8e,0x46,0x1d,0xd4, + 0x31,0x84,0x0a,0xd4,0xcf,0xba,0x4b,0x5c,0xac,0x2e,0x93,0x6e,0xf1,0xf6,0x05,0xed, + 0x34,0x80,0x12,0x5a,0xfd,0xf9,0x17,0x44,0xae,0xd4,0x4e,0x17,0xba,0x3b,0x03,0xf5, + 0x11,0x56,0x0e,0x21,0xc5,0x20,0x7c,0x33,0xd8,0x69,0x75,0xbb,0x3e,0x5b,0xab,0x23, + 0x9d,0x0c,0x37,0x10,0xfa,0x01,0x0f,0x69,0xb6,0x3f,0xf8,0x02,0x00,0x00, }; const uint8_t WEBCONFIG_HTML_GZ[] PROGMEM = { diff --git a/test/test_webconfig_gzip.py b/test/test_webconfig_gzip.py index 397273cc..f2379ad2 100644 --- a/test/test_webconfig_gzip.py +++ b/test/test_webconfig_gzip.py @@ -52,6 +52,13 @@ class WebConfigGzipTest(unittest.TestCase): for page in (ROOT / "webui/pages").glob("*.html"): self.assertEqual(gzip.decompress(blobs["MESH_HTML_" + page.stem.upper()]), page.read_bytes()) + def test_loader_matches_legacy_android_browser_syntax(self): + blobs = arrays((ROOT / "src/helpers/esp32/WebConfigHtml.h").read_text()) + loader = gzip.decompress(blobs["WEBCONFIG_HTML_LOADER"]).decode() + script = loader.split("", 1)[0] + self.assertNotIn("=>", script) + self.assertNotRegex(script, r"\b(?:async|await|let|const)\b") + def test_gzip_binding_uses_1000_iterations_without_changing_raw_ota_default(self): with mock.patch.object(zopfli_compress._gzip, "compress", return_value=gzip.compress(b"page", mtime=0)) as compress: