Fix async WiFi scanning and captive portal redirects

- Fix captive portal redirect to preserve URL path and query params
- Fix password field rendering for WiFi networks (encryption detection)
- Add WiFiPollingJS template for AJAX polling (currently disabled due to size limit)
- Remove blocking WiFi scan operations
- Add debug output for response handling
This commit is contained in:
2025-11-05 23:19:26 +10:00
parent f5db6b13d1
commit f119b02815
3 changed files with 52 additions and 10 deletions
@@ -66,12 +66,17 @@ function updateNetworkList(data) {
} else if(data.networks && data.networks.length > 0) {
data.networks.forEach(function(network) {
let qualityPercent = network.quality + '%';
let encrypted = network.enc_type !== 0 ? '<span class="l">🔒</span>' : '';
// Map quality 0-100 to icon level 1-4
let qualityIcon = Math.round((network.quality / 100) * 3) + 1;
if(qualityIcon < 1) qualityIcon = 1;
if(qualityIcon > 4) qualityIcon = 4;
// Encryption class: 'l' if encrypted, empty if open
let encClass = network.enc_type !== 0 ? 'l' : '';
let ssidEscaped = escapeHtml(network.ssid);
// Match original template structure: icon div with quality + encryption class, then percentage div
html += '<div><a href="#p" onclick="c(this)" data-ssid="' + ssidEscaped + '">' + ssidEscaped + '</a>';
html += '<div role="img" aria-label="' + qualityPercent + '" title="' + qualityPercent + '" class="q q-' + network.quality + '"></div>';
html += '<div role="img" aria-label="' + qualityPercent + '" title="' + qualityPercent + '" class="q q-' + qualityIcon + ' ' + encClass + '"></div>';
html += '<div class="q">' + qualityPercent + '</div>';
html += encrypted;
html += '</div>';
});
}
+6 -3
View File
@@ -31,10 +31,13 @@ const char HTTP_HEAD_START[] PROGMEM = "<!DOCTYPE html>"
const char HTTP_SCRIPT[] PROGMEM = "<script>function c(l){"
"document.getElementById('s').value=l.getAttribute('data-ssid')||l.innerText||l.textContent;"
"p = l.nextElementSibling.classList.contains('l');"
"document.getElementById('p').disabled = !p;"
"if(p)document.getElementById('p').focus();};"
"var parent = l.parentElement;"
"var isEncrypted = parent.querySelector('.l') !== null || (l.nextElementSibling && l.nextElementSibling.classList.contains('l'));"
"var pField = document.getElementById('p');"
"if(isEncrypted){pField.removeAttribute('disabled');pField.focus();}else{pField.setAttribute('disabled','');};"
"};"
"function f() {var x = document.getElementById('p');x.type==='password'?x.type='text':x.type='password';}"
"window.addEventListener('load',function(){var pField = document.getElementById('p');pField.removeAttribute('disabled');});"
"</script>"; // @todo add button states, disable on click , show ack , spinner etc
const char HTTP_HEAD_END[] PROGMEM = "</head><body class='{c}'><div class='wrap'>"; // {c} = _bodyclass
+38 -4
View File
@@ -1382,7 +1382,14 @@ void WiFiManager::handleRoot(AsyncWebServerRequest *request) {
void WiFiManager::handleWifi(AsyncWebServerRequest *request, boolean scan) {
#ifdef WM_DEBUG_LEVEL
DEBUG_WM(WM_DEBUG_VERBOSE, F("<- HTTP Wifi"));
DEBUG_WM(WM_DEBUG_DEV, F("handleWifi called, scan="), scan ? "true" : "false");
#endif
if (captivePortal(request)) {
#ifdef WM_DEBUG_LEVEL
DEBUG_WM(WM_DEBUG_DEV, F("Captive portal redirect"));
#endif
return; // If captive portal redirect instead of displaying the page
}
handleRequest(request);
String page = getHTTPHead(FPSTR(S_titlewifi), FPSTR(C_wifi)); // @token titlewifi
if (scan) {
@@ -1441,15 +1448,29 @@ void WiFiManager::handleWifi(AsyncWebServerRequest *request, boolean scan) {
if(_showBack) page += FPSTR(HTTP_BACKBTN);
reportStatus(page);
// Add JavaScript for AJAX polling
page += FPSTR(WIFI_POLLING_JS);
// TODO: Known issue - Adding WIFI_POLLING_JS causes silent failure (empty response)
// This is likely due to:
// 1. String size limit: Page is already ~9-10KB, adding ~2.5KB JS may exceed ESPAsyncWebServer limits
// 2. Memory fragmentation: Large String append operations on ESP8266 can cause heap fragmentation
// 3. PROGMEM access: FPSTR() on large PROGMEM strings may have timing issues with async operations
// 4. Response size: ESPAsyncWebServer may silently fail or truncate responses exceeding ~12KB
// Workaround: Temporarily disabled until we can optimize the page size or use chunked responses
// page += FPSTR(WIFI_POLLING_JS);
page += getHTTPEnd();
#ifdef WM_DEBUG_LEVEL
DEBUG_WM(WM_DEBUG_DEV, F("Page length: "), String(page.length()));
DEBUG_WM(WM_DEBUG_DEV, F("_numNetworks: "), String(_numNetworks));
DEBUG_WM(WM_DEBUG_DEV, F("_scanInProgress: "), _scanInProgress ? "true" : "false");
DEBUG_WM(WM_DEBUG_DEV, F("_lastscan: "), String(_lastscan));
DEBUG_WM(WM_DEBUG_DEV, F("About to send response"));
#endif
request->send(200, FPSTR(HTTP_HEAD_CT), page);
#ifdef WM_DEBUG_LEVEL
DEBUG_WM(WM_DEBUG_DEV, F("Sent config page"));
DEBUG_WM(WM_DEBUG_DEV, F("Response sent"));
#endif
}
@@ -2638,8 +2659,21 @@ boolean WiFiManager::captivePortal(AsyncWebServerRequest *request) {
#ifdef WM_DEBUG_LEVEL
DEBUG_WM(WM_DEBUG_VERBOSE, F("<- Request redirected to captive portal"));
DEBUG_WM(WM_DEBUG_DEV, "serverLoc " + serverLoc);
DEBUG_WM(WM_DEBUG_DEV, "Original URL " + request->url());
#endif
request->redirect((String)F("http://") + serverLoc);
// Preserve the original path in the redirect
String redirectUrl = (String)F("http://") + serverLoc + request->url();
if (request->params() > 0) {
redirectUrl += F("?");
for (size_t i = 0; i < request->params(); i++) {
if (i > 0) redirectUrl += F("&");
redirectUrl += request->getParam(i)->name() + F("=") + request->getParam(i)->value();
}
}
#ifdef WM_DEBUG_LEVEL
DEBUG_WM(WM_DEBUG_DEV, "Redirect URL " + redirectUrl);
#endif
request->redirect(redirectUrl);
return true;
}
return false;