mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-05 14:55:08 +00:00
fix(wifi): make Connect work from the setup AP (#571)
* fix(wifi): make Connect work from the setup AP Joining a network from LEDMatrix-Setup has to take the AP down first, which drops the phone that sent the request. The connect endpoint answered only after the attempt finished, so the browser never got a reply and the WiFi tab's Connect button appeared to do nothing. - /wifi/connect answers 202 immediately while the AP is active and connects in a background thread; the result (never the password) is reported via /wifi/status as last_connect_attempt. A second connect while one is pending gets 409. - connect_to_network holds a /tmp flag for the attempt; the monitor daemon skips AP management while it is fresh. Previously the daemon's disconnected counter, accumulated over the whole AP session, re-enabled the AP on its next tick in the middle of the connect. - The WiFi tab and captive setup page explain the handoff up front, and on reopening show why the last attempt failed. The wrong-password message now works: the route sets the error_type the captive page checks. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(wifi): serialize connect attempts on both paths Addresses CodeRabbit review on #571: - Check for a pending attempt before branching on AP state. A background attempt takes the AP down long before it finishes, so a second click used to bypass the 409 and start a competing synchronous connect. - Record pending for the synchronous (non-AP) path too, so two requests can't overlap and have the first clear the daemon's in-progress flag while the second is still connecting. - Clear the pending state if the background thread fails to start, rather than refusing every later request until restart. - Say the setup network returns "within a few minutes": a stale flag plus the daemon's grace period can take longer than one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -197,7 +197,11 @@
|
||||
<!-- Messages -->
|
||||
<div x-show="message"
|
||||
x-text="message"
|
||||
:class="messageType === 'error' ? 'bg-red-50 border-red-200 text-red-800' : 'bg-green-50 border-green-200 text-green-800'"
|
||||
:class="{
|
||||
error: 'bg-red-50 border-red-200 text-red-800',
|
||||
info: 'bg-blue-50 border-blue-200 text-blue-800',
|
||||
warning: 'bg-yellow-50 border-yellow-200 text-yellow-800'
|
||||
}[messageType] || 'bg-green-50 border-green-200 text-green-800'"
|
||||
class="mt-4 p-3 rounded border"
|
||||
x-cloak></div>
|
||||
</div>
|
||||
@@ -214,6 +218,7 @@ function wifiSetup() {
|
||||
ap_mode_active: false,
|
||||
auto_enable_ap_mode: true // Default: true (safe due to grace period)
|
||||
},
|
||||
statusLoaded: false,
|
||||
networks: [],
|
||||
selectedSSID: '',
|
||||
manualSSID: '',
|
||||
@@ -258,7 +263,15 @@ function wifiSetup() {
|
||||
const response = await fetch('/api/v3/wifi/status');
|
||||
const data = await response.json();
|
||||
if (data.status === 'success') {
|
||||
const firstLoad = !this.statusLoaded;
|
||||
this.status = data.data;
|
||||
this.statusLoaded = true;
|
||||
// A failed attempt brings LEDMatrix-Setup back; tell the
|
||||
// user who rejoined it why.
|
||||
const attempt = data.data.last_connect_attempt;
|
||||
if (firstLoad && attempt && attempt.state === 'failed' && !data.data.connected) {
|
||||
this.showMessage(this.describeFailedAttempt(attempt), 'error');
|
||||
}
|
||||
} else {
|
||||
this.showMessage(data.message, 'error');
|
||||
}
|
||||
@@ -316,6 +329,10 @@ function wifiSetup() {
|
||||
|
||||
this.connecting = true;
|
||||
this.message = '';
|
||||
const viaSetupNetwork = this.status.ap_mode_active;
|
||||
if (viaSetupNetwork) {
|
||||
this.showMessage(this.handoffMessage(ssid.trim()), 'info');
|
||||
}
|
||||
try {
|
||||
const response = await fetch('/api/v3/wifi/connect', {
|
||||
method: 'POST',
|
||||
@@ -327,7 +344,16 @@ function wifiSetup() {
|
||||
password: this.password || ''
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
if (response.status === 202) {
|
||||
// Connecting in the background; the setup network is about
|
||||
// to go down and take this page's connection with it.
|
||||
this.password = '';
|
||||
this.showMessage(this.handoffMessage(ssid.trim()), 'info');
|
||||
this.pollConnectResult(ssid.trim());
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if response is ok before parsing JSON
|
||||
if (!response.ok) {
|
||||
// Try to parse error message from response
|
||||
@@ -362,13 +388,56 @@ function wifiSetup() {
|
||||
this.showMessage(data.message || 'Failed to connect to network', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('WiFi connect error:', error);
|
||||
this.showMessage('Failed to connect: ' + error.message, 'error');
|
||||
if (viaSetupNetwork) {
|
||||
// The setup network went down mid-request: that is the
|
||||
// connection attempt happening, not a failure.
|
||||
this.showMessage(this.handoffMessage(ssid.trim()), 'info');
|
||||
} else {
|
||||
console.error('WiFi connect error:', error);
|
||||
this.showMessage('Failed to connect: ' + error.message, 'error');
|
||||
}
|
||||
} finally {
|
||||
this.connecting = false;
|
||||
}
|
||||
},
|
||||
|
||||
handoffMessage(ssid) {
|
||||
return `Connecting to ${ssid}. LEDMatrix-Setup will turn off, so this page will stop responding. ` +
|
||||
`If it works, the LED display shows the new address; rejoin your home WiFi and open it. ` +
|
||||
`If it fails, LEDMatrix-Setup comes back within a few minutes: rejoin it and reopen this page to see why.`;
|
||||
},
|
||||
|
||||
// Follow a background connect until its result lands or the setup
|
||||
// network drops out from under this page.
|
||||
async pollConnectResult(ssid, retriesLeft = 20) {
|
||||
if (retriesLeft <= 0) return;
|
||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||
try {
|
||||
const response = await fetch('/api/v3/wifi/status');
|
||||
const data = await response.json();
|
||||
const attempt = data.data && data.data.last_connect_attempt;
|
||||
if (attempt && attempt.ssid === ssid && attempt.state !== 'pending') {
|
||||
this.status = data.data;
|
||||
if (attempt.state === 'success') {
|
||||
this.showMessage(attempt.message || `Connected to ${ssid}`, 'success');
|
||||
} else {
|
||||
this.showMessage(this.describeFailedAttempt(attempt), 'error');
|
||||
}
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
return; // Setup network is down; the handoff message already explains.
|
||||
}
|
||||
this.pollConnectResult(ssid, retriesLeft - 1);
|
||||
},
|
||||
|
||||
describeFailedAttempt(attempt) {
|
||||
const reason = attempt.error_type === 'wrong_password'
|
||||
? 'the password was incorrect'
|
||||
: (attempt.message || 'unknown error');
|
||||
return `Could not connect to ${attempt.ssid}: ${reason}.`;
|
||||
},
|
||||
|
||||
async disconnectFromNetwork() {
|
||||
if (!this.status.connected) {
|
||||
this.showMessage('Not connected to any network', 'warning');
|
||||
|
||||
Reference in New Issue
Block a user