Fix captive portal redirect: visible countdown, keep AP up until it finishes

Previously the softAP was torn down (esp_restart) only 1s after sending
the success page, while the page's own redirect timer waited 7s -- so
the AP (and the phone's captive-portal session with it) was gone long
before the redirect could fire. Now the page shows a live 10s countdown
before redirecting, and the device holds the AP up for 11s so the
countdown always completes. Also added a "Redirect now" button for a
phone that's already reconnected to normal WiFi.
This commit is contained in:
2026-07-22 09:45:59 -04:00
parent 9f9ad34a40
commit 462b558bef
+36 -15
View File
@@ -448,11 +448,17 @@ static esp_err_t save_config_post_handler(httpd_req_t *req)
/* The success page hands the browser off to the server's claim page,
* carrying this device's id -- how a frame gets linked to a user
* account. The ~7s delay covers the phone dropping this softAP (the
* device reboots right after this response) and rejoining its normal
* WiFi before the redirect fires; the visible link is the fallback
* if the phone loses that race. Scheme handling matches
* frame_client.c's build_url(): a bare host gets http://. */
* account. This page is entirely self-contained (no external
* resources) so it renders fully from what we send now, before the
* softAP goes away -- a phone mid-load of a remote asset would just
* time out once the AP drops. The visible countdown ticks down for
* PROVISIONING_COUNTDOWN_S seconds and then redirects; the AP is kept
* alive for one second longer than that (see the vTaskDelay below) so
* the countdown always finishes, and the phone has that whole window
* to rejoin its normal WiFi and let the redirect land on the real
* server. "Redirect now" covers a phone that's already reconnected.
* Scheme handling matches frame_client.c's build_url(): a bare host
* gets http://. */
char device_id[FRAME_DEVICE_ID_LEN + 1];
frame_device_id_get(device_id, sizeof(device_id));
@@ -463,25 +469,40 @@ static esp_err_t save_config_post_handler(httpd_req_t *req)
}
snprintf(claim_url, sizeof(claim_url), "%s%s/claim?device_id=%s", scheme, cfg.toolsserver, device_id);
char resp[1024];
#define PROVISIONING_COUNTDOWN_S 10
char resp[1536];
snprintf(resp, sizeof(resp),
"<!doctype html><html><head>"
"<meta http-equiv=\"refresh\" content=\"7;url=%s\">"
"<style>body{font-family:sans-serif;text-align:center;padding:2em}</style></head>"
"<meta http-equiv=\"refresh\" content=\"%d;url=%s\">"
"<style>body{font-family:sans-serif;text-align:center;padding:2em}"
"#now{display:inline-block;margin-top:1em;padding:.6em 1.2em;"
"background:#2563eb;color:#fff;text-decoration:none;border-radius:8px}</style></head>"
"<body><h3>Saved &mdash; the frame is restarting</h3>"
"<p>Reconnect to your normal WiFi. You'll be taken to the claim page "
"in a few seconds&hellip;</p>"
"<p><a href=\"%s\">Continue to claim your frame</a></p>"
"<script>setTimeout(function(){location.href=%c%s%c},7000)</script>"
"<p>Reconnect to your normal WiFi if it doesn't happen automatically.</p>"
"<p>Redirecting you in <span id=\"n\">%d</span> seconds&hellip;</p>"
"<p><a id=\"now\" href=\"%s\">Redirect now</a></p>"
"<script>"
"var n=%d,e=document.getElementById('n');"
"var t=setInterval(function(){"
"n--;if(e)e.textContent=n;"
"if(n<=0){clearInterval(t);location.href='%s';}"
"},1000);"
"</script>"
"</body></html>",
claim_url, claim_url, '"', claim_url, '"');
PROVISIONING_COUNTDOWN_S, claim_url, PROVISIONING_COUNTDOWN_S, claim_url,
PROVISIONING_COUNTDOWN_S, claim_url);
httpd_resp_set_type(req, "text/html");
httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN);
/* Let the response flush to the client before rebooting into STA mode. */
vTaskDelay(pdMS_TO_TICKS(1000));
/* Keep the softAP up for the full visible countdown (plus a 1s margin
* for the response to flush and the JS timer to fire) before tearing
* it down -- see the comment above for why. */
vTaskDelay(pdMS_TO_TICKS((PROVISIONING_COUNTDOWN_S + 1) * 1000));
esp_restart();
#undef PROVISIONING_COUNTDOWN_S
return ESP_OK;
}