From dd3c59499f0f3178de36d878b29f441f1c4a85b3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 01:44:55 +0000 Subject: [PATCH 01/47] Initial plan From dd68a69f833461da23ebecf1108ca49ce7e9c88e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 01:51:16 +0000 Subject: [PATCH 02/47] Implement slot availability/permission check for wardriving throttle Co-authored-by: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com> --- content/wardrive.js | 129 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 128 insertions(+), 1 deletion(-) diff --git a/content/wardrive.js b/content/wardrive.js index e56379c..8f16968 100644 --- a/content/wardrive.js +++ b/content/wardrive.js @@ -5,7 +5,7 @@ // - Manual "Send Ping" and Auto mode (interval selectable: 15/30/60s) // - Acquire wake lock during auto mode to keep screen awake -import { WebBleConnection, Constants, Packet } from "./mc/index.js"; // your BLE client +import { WebBleConnection, Constants, Packet, BufferUtils } from "./mc/index.js"; // your BLE client // ---- Debug Configuration ---- // Enable debug logging via URL parameter (?debug=true) or set default here @@ -75,6 +75,7 @@ const MIN_PING_DISTANCE_M = 25; // Minimum distance (25m) between pings // MeshMapper API Configuration const MESHMAPPER_API_URL = "https://yow.meshmapper.net/wardriving-api.php"; +const MESHMAPPER_CAPACITY_CHECK_URL = "https://yow.meshmapper.net/capacitycheck.php"; const MESHMAPPER_API_KEY = "59C7754DABDF5C11CA5F5D8368F89"; const MESHMAPPER_DEFAULT_WHO = "GOME-WarDriver"; // Default identifier @@ -121,6 +122,7 @@ const state = { lastSuccessfulPingLocation: null, // { lat, lon } of the last successful ping (Mesh + API) distanceUpdateTimer: null, // Timer for updating distance display capturedPingCoords: null, // { lat, lon, accuracy } captured at ping time, used for API post after 7s delay + devicePublicKey: null, // Hex string of device's public key (used for capacity check) repeaterTracking: { isListening: false, // Whether we're currently listening for echoes sentTimestamp: null, // Timestamp when the ping was sent @@ -451,6 +453,9 @@ function cleanupAllTimers() { // Clear captured ping coordinates state.capturedPingCoords = null; + + // Clear device public key + state.devicePublicKey = null; } function enableControls(connected) { @@ -1004,6 +1009,73 @@ function getDeviceIdentifier() { return (deviceText && deviceText !== "—") ? deviceText : MESHMAPPER_DEFAULT_WHO; } +/** + * Check capacity / slot availability with MeshMapper API + * @param {string} reason - Either "connect" (acquire slot) or "disconnect" (release slot) + * @returns {Promise} True if allowed to continue, false otherwise + */ +async function checkCapacity(reason) { + // Validate public key exists + if (!state.devicePublicKey) { + debugError("checkCapacity called but no public key stored"); + return reason === "connect" ? false : true; // Fail closed on connect, allow disconnect + } + + // Set status for connect requests + if (reason === "connect") { + setStatus("Acquiring wardriving slot", STATUS_COLORS.info); + } + + try { + const payload = { + key: MESHMAPPER_API_KEY, + public_key: state.devicePublicKey, + who: getDeviceIdentifier(), + reason: reason + }; + + debugLog(`Checking capacity: reason=${reason}, public_key=${state.devicePublicKey.substring(0, 16)}..., who=${payload.who}`); + + const response = await fetch(MESHMAPPER_CAPACITY_CHECK_URL, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload) + }); + + if (!response.ok) { + debugWarn(`Capacity check API returned error status ${response.status}`); + // Fail open on network errors for connect + if (reason === "connect") { + debugWarn("Failing open (allowing connection) due to API error"); + // Show network issue message briefly + setStatus("Network issue checking slot, proceeding anyway", STATUS_COLORS.warning); + await new Promise(resolve => setTimeout(resolve, 1500)); // Show message for 1.5s + return true; + } + return true; // Always allow disconnect to proceed + } + + const data = await response.json(); + debugLog(`Capacity check response: allowed=${data.allowed}`); + + return data.allowed === true; + + } catch (error) { + debugError(`Capacity check failed: ${error.message}`); + + // Fail open on network errors for connect + if (reason === "connect") { + debugWarn("Failing open (allowing connection) due to network error"); + // Show network issue message briefly + setStatus("Network issue checking slot, proceeding anyway", STATUS_COLORS.warning); + await new Promise(resolve => setTimeout(resolve, 1500)); // Show message for 1.5s + return true; + } + + return true; // Always allow disconnect to proceed + } +} + /** * Post wardrive ping data to MeshMapper API * @param {number} lat - Latitude @@ -1034,6 +1106,22 @@ async function postToMeshMapperAPI(lat, lon, heardRepeats) { debugWarn(`MeshMapper API returned error status ${response.status}`); } else { debugLog(`MeshMapper API post successful (status ${response.status})`); + + // Parse response and check if we're allowed to continue + try { + const data = await response.json(); + if (data.allowed === false) { + debugWarn("MeshMapper API returned allowed=false, disconnecting"); + setStatus("WarDriving app has reached capacity or is down", STATUS_COLORS.error); + // Disconnect after a brief delay to ensure user sees the message + setTimeout(() => { + disconnect().catch(err => debugError(`Disconnect after capacity denial failed: ${err.message}`)); + }, 1500); + } + } catch (parseError) { + debugWarn(`Failed to parse MeshMapper API response: ${parseError.message}`); + // Continue operation if we can't parse the response + } } } catch (error) { // Log error but don't fail the ping @@ -1928,6 +2016,22 @@ async function connect() { connectBtn.disabled = false; const selfInfo = await conn.getSelfInfo(); debugLog(`Device info: ${selfInfo?.name || "[No device]"}`); + + // Validate and store public key + if (!selfInfo?.publicKey || selfInfo.publicKey.length !== 32) { + debugError("Missing or invalid public key from device", selfInfo?.publicKey); + setStatus("Unable to read device public key; try again", STATUS_COLORS.error); + // Disconnect after a brief delay to ensure user sees the error message + setTimeout(() => { + disconnect().catch(err => debugError(`Disconnect after public key error failed: ${err.message}`)); + }, 1500); + return; + } + + // Convert public key to hex and store + state.devicePublicKey = BufferUtils.bytesToHex(selfInfo.publicKey); + debugLog(`Device public key stored: ${state.devicePublicKey.substring(0, 16)}...`); + deviceInfoEl.textContent = selfInfo?.name || "[No device]"; updateAutoButton(); try { @@ -1939,6 +2043,17 @@ async function connect() { try { await ensureChannel(); await primeGpsOnce(); + + // Check capacity after GPS is initialized + const allowed = await checkCapacity("connect"); + if (!allowed) { + debugWarn("Capacity check denied, disconnecting"); + setStatus("WarDriving app has reached capacity or is down", STATUS_COLORS.error); + // Disconnect after a brief delay to ensure user sees the message + setTimeout(() => { + disconnect().catch(err => debugError(`Disconnect after capacity denial failed: ${err.message}`)); + }, 1500); + } } catch (e) { debugError(`Channel setup failed: ${e.message}`, e); setStatus(e.message || "Channel setup failed", STATUS_COLORS.error); @@ -1952,6 +2067,7 @@ async function connect() { deviceInfoEl.textContent = "—"; state.connection = null; state.channel = null; + state.devicePublicKey = null; // Clear public key stopAutoPing(true); // Ignore cooldown check on disconnect enableControls(false); updateAutoButton(); @@ -1987,6 +2103,17 @@ async function disconnect() { connectBtn.disabled = true; setStatus("Disconnecting", STATUS_COLORS.info); + // Release capacity slot if we have a public key + if (state.devicePublicKey) { + try { + debugLog("Releasing capacity slot"); + await checkCapacity("disconnect"); + } catch (e) { + debugWarn(`Failed to release capacity slot: ${e.message}`); + // Don't fail disconnect if capacity release fails + } + } + // Delete the wardriving channel before disconnecting try { if (state.channel && typeof state.connection.deleteChannel === "function") { From aeaf0b29cc7f7fe01f7201f3b5eb976603a2eacc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 02:02:00 +0000 Subject: [PATCH 03/47] Update STATUS_MESSAGES.md with capacity check messages Co-authored-by: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com> --- STATUS_MESSAGES.md | 50 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/STATUS_MESSAGES.md b/STATUS_MESSAGES.md index 58f5e6a..74fbc8f 100644 --- a/STATUS_MESSAGES.md +++ b/STATUS_MESSAGES.md @@ -79,7 +79,44 @@ Status messages follow these consistent conventions: --- -### 2. Ping Operation Messages +### 2. Capacity Check Messages + +#### Acquiring wardriving slot +- **Message**: `"Acquiring wardriving slot"` +- **Color**: Sky blue (info) +- **Used in**: `checkCapacity()` +- **Source**: `content/wardrive.js:1026` +- **Context**: When connecting to device and checking if a wardriving slot is available +- **Minimum Visibility**: 500ms minimum enforced (or until API response received) + +#### WarDriving app has reached capacity or is down +- **Message**: `"WarDriving app has reached capacity or is down"` +- **Color**: Red (error) +- **Used in**: `checkCapacity()`, `postToMeshMapperAPI()` +- **Source**: `content/wardrive.js:2051`, `content/wardrive.js:1115` +- **Context**: Capacity check API denies slot on connect, or wardriving API returns allowed=false during active session +- **Minimum Visibility**: N/A (error state persists until disconnect) + +#### Unable to read device public key; try again +- **Message**: `"Unable to read device public key; try again"` +- **Color**: Red (error) +- **Used in**: `connect()` +- **Source**: `content/wardrive.js:2023` +- **Context**: Device public key is missing or invalid when trying to acquire capacity slot +- **Minimum Visibility**: N/A (error state persists until disconnect) + +#### Network issue checking slot, proceeding anyway +- **Message**: `"Network issue checking slot, proceeding anyway"` +- **Color**: Amber (warning) +- **Used in**: `checkCapacity()` +- **Source**: `content/wardrive.js:1051`, `content/wardrive.js:1070` +- **Context**: Capacity check API is unreachable or returns error during connect (fail-open behavior) +- **Minimum Visibility**: 1500ms enforced (brief warning before continuing) +- **Notes**: Implements fail-open policy - allows connection to proceed despite API failure + +--- + +### 3. Ping Operation Messages #### Sending manual ping - **Message**: `"Sending manual ping"` @@ -140,7 +177,7 @@ Status messages follow these consistent conventions: --- -### 3. GPS Status Messages +### 4. GPS Status Messages #### Waiting for GPS fix - **Message**: `"Waiting for GPS fix"` @@ -160,7 +197,7 @@ Status messages follow these consistent conventions: --- -### 4. Countdown Timer Messages +### 5. Countdown Timer Messages These messages use a hybrid approach: **first display respects 500ms minimum**, then updates occur immediately every second. @@ -216,7 +253,7 @@ These messages use a hybrid approach: **first display respects 500ms minimum**, --- -### 5. API and Map Update Messages +### 6. API and Map Update Messages #### Posting to API - **Message**: `"Posting to API"` @@ -238,7 +275,7 @@ These messages use a hybrid approach: **first display respects 500ms minimum**, --- -### 6. Auto Mode Messages +### 7. Auto Mode Messages #### Auto mode stopped - **Message**: `"Auto mode stopped"` @@ -323,8 +360,9 @@ Result: "Message A" (visible 500ms) → "Message C" ## Summary -**Total Status Messages**: 25 unique message patterns +**Total Status Messages**: 29 unique message patterns - **Connection**: 7 messages +- **Capacity Check**: 4 messages - **Ping Operation**: 6 messages (consolidated "Ping sent" for both manual and auto) - **GPS**: 2 messages - **Countdown Timers**: 6 message patterns (with dynamic countdown values) From 7e06ba43dbe5e26e2a773f84e3b7bf585e1b27f7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 02:11:36 +0000 Subject: [PATCH 04/47] Initial plan From be674cc867f864898cf5743ed31856bd678a7f31 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 02:14:33 +0000 Subject: [PATCH 05/47] Change capacity check from fail-open to fail-closed Co-authored-by: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com> --- STATUS_MESSAGES.md | 18 +++++++++--------- content/wardrive.js | 20 ++++++++------------ 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/STATUS_MESSAGES.md b/STATUS_MESSAGES.md index 74fbc8f..2ae1a8f 100644 --- a/STATUS_MESSAGES.md +++ b/STATUS_MESSAGES.md @@ -93,9 +93,10 @@ Status messages follow these consistent conventions: - **Message**: `"WarDriving app has reached capacity or is down"` - **Color**: Red (error) - **Used in**: `checkCapacity()`, `postToMeshMapperAPI()` -- **Source**: `content/wardrive.js:2051`, `content/wardrive.js:1115` -- **Context**: Capacity check API denies slot on connect, or wardriving API returns allowed=false during active session +- **Source**: `content/wardrive.js:2051`, `content/wardrive.js:1115`, `content/wardrive.js:1049`, `content/wardrive.js:1068` +- **Context**: Capacity check API denies slot on connect, returns error status, network is unreachable, or wardriving API returns allowed=false during active session - **Minimum Visibility**: N/A (error state persists until disconnect) +- **Notes**: Implements fail-closed policy - connection is denied if API fails or is unreachable #### Unable to read device public key; try again - **Message**: `"Unable to read device public key; try again"` @@ -106,13 +107,12 @@ Status messages follow these consistent conventions: - **Minimum Visibility**: N/A (error state persists until disconnect) #### Network issue checking slot, proceeding anyway -- **Message**: `"Network issue checking slot, proceeding anyway"` +- **Message**: `"Network issue checking slot, proceeding anyway"` (DEPRECATED - no longer used) - **Color**: Amber (warning) -- **Used in**: `checkCapacity()` -- **Source**: `content/wardrive.js:1051`, `content/wardrive.js:1070` -- **Context**: Capacity check API is unreachable or returns error during connect (fail-open behavior) -- **Minimum Visibility**: 1500ms enforced (brief warning before continuing) -- **Notes**: Implements fail-open policy - allows connection to proceed despite API failure +- **Used in**: N/A (removed) +- **Source**: Previously `content/wardrive.js:1051`, `content/wardrive.js:1070` +- **Context**: This message is no longer shown. Network issues now result in connection denial (fail-closed) +- **Notes**: Replaced by fail-closed policy - connection is now denied on network errors --- @@ -362,7 +362,7 @@ Result: "Message A" (visible 500ms) → "Message C" **Total Status Messages**: 29 unique message patterns - **Connection**: 7 messages -- **Capacity Check**: 4 messages +- **Capacity Check**: 3 messages (1 deprecated) - **Ping Operation**: 6 messages (consolidated "Ping sent" for both manual and auto) - **GPS**: 2 messages - **Countdown Timers**: 6 message patterns (with dynamic countdown values) diff --git a/content/wardrive.js b/content/wardrive.js index 8f16968..e25a825 100644 --- a/content/wardrive.js +++ b/content/wardrive.js @@ -1044,13 +1044,11 @@ async function checkCapacity(reason) { if (!response.ok) { debugWarn(`Capacity check API returned error status ${response.status}`); - // Fail open on network errors for connect + // Fail closed on network errors for connect if (reason === "connect") { - debugWarn("Failing open (allowing connection) due to API error"); - // Show network issue message briefly - setStatus("Network issue checking slot, proceeding anyway", STATUS_COLORS.warning); - await new Promise(resolve => setTimeout(resolve, 1500)); // Show message for 1.5s - return true; + debugError("Failing closed (denying connection) due to API error"); + setStatus("WarDriving app has reached capacity or is down", STATUS_COLORS.error); + return false; } return true; // Always allow disconnect to proceed } @@ -1063,13 +1061,11 @@ async function checkCapacity(reason) { } catch (error) { debugError(`Capacity check failed: ${error.message}`); - // Fail open on network errors for connect + // Fail closed on network errors for connect if (reason === "connect") { - debugWarn("Failing open (allowing connection) due to network error"); - // Show network issue message briefly - setStatus("Network issue checking slot, proceeding anyway", STATUS_COLORS.warning); - await new Promise(resolve => setTimeout(resolve, 1500)); // Show message for 1.5s - return true; + debugError("Failing closed (denying connection) due to network error"); + setStatus("WarDriving app has reached capacity or is down", STATUS_COLORS.error); + return false; } return true; // Always allow disconnect to proceed From 66ce76fb9dca8c27c4fab6bd053c1b4eacf4e0f9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 02:24:10 +0000 Subject: [PATCH 06/47] Separate capacity status messages: capacity full vs app down Co-authored-by: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com> --- STATUS_MESSAGES.md | 21 +++++++++++++++------ content/wardrive.js | 13 +++++++++---- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/STATUS_MESSAGES.md b/STATUS_MESSAGES.md index 2ae1a8f..3329f01 100644 --- a/STATUS_MESSAGES.md +++ b/STATUS_MESSAGES.md @@ -89,12 +89,21 @@ Status messages follow these consistent conventions: - **Context**: When connecting to device and checking if a wardriving slot is available - **Minimum Visibility**: 500ms minimum enforced (or until API response received) -#### WarDriving app has reached capacity or is down -- **Message**: `"WarDriving app has reached capacity or is down"` +#### WarDriving app has reached capacity +- **Message**: `"WarDriving app has reached capacity"` - **Color**: Red (error) - **Used in**: `checkCapacity()`, `postToMeshMapperAPI()` -- **Source**: `content/wardrive.js:2051`, `content/wardrive.js:1115`, `content/wardrive.js:1049`, `content/wardrive.js:1068` -- **Context**: Capacity check API denies slot on connect, returns error status, network is unreachable, or wardriving API returns allowed=false during active session +- **Source**: `content/wardrive.js:1060`, `content/wardrive.js:1111` +- **Context**: Capacity check API denies slot on connect (returns allowed=false), or wardriving API returns allowed=false during active session +- **Minimum Visibility**: N/A (error state persists until disconnect) +- **Notes**: Displayed when the API successfully responds but indicates capacity is full + +#### WarDriving app is down +- **Message**: `"WarDriving app is down"` +- **Color**: Red (error) +- **Used in**: `checkCapacity()` +- **Source**: `content/wardrive.js:1050`, `content/wardrive.js:1067` +- **Context**: Capacity check API returns error status or network is unreachable during connect - **Minimum Visibility**: N/A (error state persists until disconnect) - **Notes**: Implements fail-closed policy - connection is denied if API fails or is unreachable @@ -360,9 +369,9 @@ Result: "Message A" (visible 500ms) → "Message C" ## Summary -**Total Status Messages**: 29 unique message patterns +**Total Status Messages**: 30 unique message patterns - **Connection**: 7 messages -- **Capacity Check**: 3 messages (1 deprecated) +- **Capacity Check**: 4 messages (1 deprecated) - **Ping Operation**: 6 messages (consolidated "Ping sent" for both manual and auto) - **GPS**: 2 messages - **Countdown Timers**: 6 message patterns (with dynamic countdown values) diff --git a/content/wardrive.js b/content/wardrive.js index e25a825..11c7831 100644 --- a/content/wardrive.js +++ b/content/wardrive.js @@ -1047,7 +1047,7 @@ async function checkCapacity(reason) { // Fail closed on network errors for connect if (reason === "connect") { debugError("Failing closed (denying connection) due to API error"); - setStatus("WarDriving app has reached capacity or is down", STATUS_COLORS.error); + setStatus("WarDriving app is down", STATUS_COLORS.error); return false; } return true; // Always allow disconnect to proceed @@ -1056,6 +1056,11 @@ async function checkCapacity(reason) { const data = await response.json(); debugLog(`Capacity check response: allowed=${data.allowed}`); + // Handle capacity full vs. allowed cases separately + if (data.allowed === false && reason === "connect") { + setStatus("WarDriving app has reached capacity", STATUS_COLORS.error); + } + return data.allowed === true; } catch (error) { @@ -1064,7 +1069,7 @@ async function checkCapacity(reason) { // Fail closed on network errors for connect if (reason === "connect") { debugError("Failing closed (denying connection) due to network error"); - setStatus("WarDriving app has reached capacity or is down", STATUS_COLORS.error); + setStatus("WarDriving app is down", STATUS_COLORS.error); return false; } @@ -1108,7 +1113,7 @@ async function postToMeshMapperAPI(lat, lon, heardRepeats) { const data = await response.json(); if (data.allowed === false) { debugWarn("MeshMapper API returned allowed=false, disconnecting"); - setStatus("WarDriving app has reached capacity or is down", STATUS_COLORS.error); + setStatus("WarDriving app has reached capacity", STATUS_COLORS.error); // Disconnect after a brief delay to ensure user sees the message setTimeout(() => { disconnect().catch(err => debugError(`Disconnect after capacity denial failed: ${err.message}`)); @@ -2044,7 +2049,7 @@ async function connect() { const allowed = await checkCapacity("connect"); if (!allowed) { debugWarn("Capacity check denied, disconnecting"); - setStatus("WarDriving app has reached capacity or is down", STATUS_COLORS.error); + // Status message already set by checkCapacity() // Disconnect after a brief delay to ensure user sees the message setTimeout(() => { disconnect().catch(err => debugError(`Disconnect after capacity denial failed: ${err.message}`)); From ef5fd2322a7984e9ac7363b2ef327c1da3bb461e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 02:25:48 +0000 Subject: [PATCH 07/47] Set status to Idle after successful capacity check and connection complete Co-authored-by: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com> --- STATUS_MESSAGES.md | 6 +++--- content/wardrive.js | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/STATUS_MESSAGES.md b/STATUS_MESSAGES.md index 3329f01..2132255 100644 --- a/STATUS_MESSAGES.md +++ b/STATUS_MESSAGES.md @@ -277,9 +277,9 @@ These messages use a hybrid approach: **first display respects 500ms minimum**, #### Idle - **Message**: `"Idle"` - **Color**: Slate (idle) -- **Used in**: `postApiAndRefreshMap()` -- **Source**: `content/wardrive.js:1091` -- **Context**: Manual mode, after API post completes +- **Used in**: `connect()`, `postApiAndRefreshMap()` +- **Source**: `content/wardrive.js:2060`, `content/wardrive.js:1091` +- **Context**: Initial connection complete after capacity check succeeds, or manual mode after API post completes - **Minimum Visibility**: 500ms minimum enforced --- diff --git a/content/wardrive.js b/content/wardrive.js index 11c7831..cb7c4ad 100644 --- a/content/wardrive.js +++ b/content/wardrive.js @@ -2054,6 +2054,9 @@ async function connect() { setTimeout(() => { disconnect().catch(err => debugError(`Disconnect after capacity denial failed: ${err.message}`)); }, 1500); + } else { + // Connection complete, set status to Idle + setStatus("Idle", STATUS_COLORS.idle); } } catch (e) { debugError(`Channel setup failed: ${e.message}`, e); From db78f31fe6089f16ab00ba4ae2a7503cecc26487 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 02:27:11 +0000 Subject: [PATCH 08/47] Fix line number references in STATUS_MESSAGES.md Co-authored-by: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com> --- STATUS_MESSAGES.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/STATUS_MESSAGES.md b/STATUS_MESSAGES.md index 2132255..5caab0f 100644 --- a/STATUS_MESSAGES.md +++ b/STATUS_MESSAGES.md @@ -93,7 +93,7 @@ Status messages follow these consistent conventions: - **Message**: `"WarDriving app has reached capacity"` - **Color**: Red (error) - **Used in**: `checkCapacity()`, `postToMeshMapperAPI()` -- **Source**: `content/wardrive.js:1060`, `content/wardrive.js:1111` +- **Source**: `content/wardrive.js:1061`, `content/wardrive.js:1116` - **Context**: Capacity check API denies slot on connect (returns allowed=false), or wardriving API returns allowed=false during active session - **Minimum Visibility**: N/A (error state persists until disconnect) - **Notes**: Displayed when the API successfully responds but indicates capacity is full @@ -102,7 +102,7 @@ Status messages follow these consistent conventions: - **Message**: `"WarDriving app is down"` - **Color**: Red (error) - **Used in**: `checkCapacity()` -- **Source**: `content/wardrive.js:1050`, `content/wardrive.js:1067` +- **Source**: `content/wardrive.js:1050`, `content/wardrive.js:1072` - **Context**: Capacity check API returns error status or network is unreachable during connect - **Minimum Visibility**: N/A (error state persists until disconnect) - **Notes**: Implements fail-closed policy - connection is denied if API fails or is unreachable From 44eac118b34fc1c70ae4d38279fb46cad7c9f6d2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 02:39:34 +0000 Subject: [PATCH 09/47] Initial plan From 999f73bd8eb421511fd38f96293139669d407f63 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 02:44:43 +0000 Subject: [PATCH 10/47] Fix connection status message flow issues Co-authored-by: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com> --- STATUS_MESSAGES.md | 43 +++++++++++++++++++++++-------------------- content/wardrive.js | 34 ++++++++++++++++++++++++++++++---- 2 files changed, 53 insertions(+), 24 deletions(-) diff --git a/STATUS_MESSAGES.md b/STATUS_MESSAGES.md index 5caab0f..205e715 100644 --- a/STATUS_MESSAGES.md +++ b/STATUS_MESSAGES.md @@ -25,39 +25,41 @@ Status messages follow these consistent conventions: - **Message**: `"Connecting"` - **Color**: Sky blue (info) - **Used in**: `connect()` -- **Source**: `content/wardrive.js:1916` -- **Context**: When user clicks Connect button -- **Minimum Visibility**: Natural async timing during BLE pairing (typically 2-5 seconds) +- **Source**: `content/wardrive.js:2005` +- **Context**: When user clicks Connect button; remains visible during entire connection process (BLE pairing, channel setup, and capacity check) +- **Minimum Visibility**: Natural async timing during full connection process (typically 3-8 seconds including capacity check) #### Connected - **Message**: `"Connected"` - **Color**: Green (success) - **Used in**: `connect()` -- **Source**: `content/wardrive.js:1926` -- **Context**: After BLE device successfully pairs -- **Minimum Visibility**: 500ms minimum enforced +- **Source**: `content/wardrive.js:2060` +- **Context**: After full connection process completes successfully (BLE paired, channel setup, and capacity check passed) +- **Minimum Visibility**: Persists until user interacts with app buttons (send ping, start auto mode) +- **Note**: This message now only appears after the complete connection handshake, not just after BLE pairing #### Disconnecting - **Message**: `"Disconnecting"` - **Color**: Sky blue (info) - **Used in**: `disconnect()` -- **Source**: `content/wardrive.js:1988` -- **Context**: When user clicks Disconnect button +- **Source**: `content/wardrive.js:2118` +- **Context**: When user clicks Disconnect button or when automatic disconnect is triggered - **Minimum Visibility**: 500ms minimum enforced #### Disconnected - **Message**: `"Disconnected"` - **Color**: Red (error) - **Used in**: `connect()`, `disconnect()`, event handlers -- **Source**: `content/wardrive.js:1950`, `content/wardrive.js:2046` -- **Context**: Initial state and when BLE device disconnects +- **Source**: `content/wardrive.js:2073`, `content/wardrive.js:2177` +- **Context**: Initial state and when BLE device disconnects normally (user-initiated or device-initiated) - **Minimum Visibility**: N/A (persists until connection is established) +- **Note**: Only shown for normal disconnections; error disconnections (e.g., app down, capacity full) preserve their specific error message #### Connection failed - **Message**: `"Connection failed"` (or error message) - **Color**: Red (error) - **Used in**: `connect()`, event handlers -- **Source**: `content/wardrive.js:1976`, `content/wardrive.js:2059` +- **Source**: `content/wardrive.js:2096`, `content/wardrive.js:2190` - **Context**: BLE connection fails or connection button error - **Minimum Visibility**: N/A (error state persists) @@ -65,7 +67,7 @@ Status messages follow these consistent conventions: - **Message**: `"Channel setup failed"` (or error message) - **Color**: Red (error) - **Used in**: `connect()` -- **Source**: `content/wardrive.js:1944` +- **Source**: `content/wardrive.js:2063` - **Context**: Channel creation or lookup fails during connection - **Minimum Visibility**: N/A (error state persists) @@ -73,7 +75,7 @@ Status messages follow these consistent conventions: - **Message**: `"Disconnect failed"` (or error message) - **Color**: Red (error) - **Used in**: `disconnect()` -- **Source**: `content/wardrive.js:2018` +- **Source**: `content/wardrive.js:2149` - **Context**: Error during disconnect operation - **Minimum Visibility**: N/A (error state persists) @@ -95,8 +97,8 @@ Status messages follow these consistent conventions: - **Used in**: `checkCapacity()`, `postToMeshMapperAPI()` - **Source**: `content/wardrive.js:1061`, `content/wardrive.js:1116` - **Context**: Capacity check API denies slot on connect (returns allowed=false), or wardriving API returns allowed=false during active session -- **Minimum Visibility**: N/A (error state persists until disconnect) -- **Notes**: Displayed when the API successfully responds but indicates capacity is full +- **Minimum Visibility**: N/A (error state persists; message is preserved during automatic disconnect) +- **Notes**: Displayed when the API successfully responds but indicates capacity is full. When this error occurs during connection, the automatic disconnect flow preserves this status message instead of showing "Disconnected" #### WarDriving app is down - **Message**: `"WarDriving app is down"` @@ -104,8 +106,8 @@ Status messages follow these consistent conventions: - **Used in**: `checkCapacity()` - **Source**: `content/wardrive.js:1050`, `content/wardrive.js:1072` - **Context**: Capacity check API returns error status or network is unreachable during connect -- **Minimum Visibility**: N/A (error state persists until disconnect) -- **Notes**: Implements fail-closed policy - connection is denied if API fails or is unreachable +- **Minimum Visibility**: N/A (error state persists; message is preserved during automatic disconnect) +- **Notes**: Implements fail-closed policy - connection is denied if API fails or is unreachable. When this error occurs, the automatic disconnect flow preserves this status message instead of showing "Disconnected" #### Unable to read device public key; try again - **Message**: `"Unable to read device public key; try again"` @@ -277,10 +279,11 @@ These messages use a hybrid approach: **first display respects 500ms minimum**, #### Idle - **Message**: `"Idle"` - **Color**: Slate (idle) -- **Used in**: `connect()`, `postApiAndRefreshMap()` -- **Source**: `content/wardrive.js:2060`, `content/wardrive.js:1091` -- **Context**: Initial connection complete after capacity check succeeds, or manual mode after API post completes +- **Used in**: `postApiAndRefreshMap()` +- **Source**: `content/wardrive.js:1091` +- **Context**: Manual mode after API post completes - **Minimum Visibility**: 500ms minimum enforced +- **Note**: No longer shown after initial connection; "Connected" status is displayed instead and persists until user action --- diff --git a/content/wardrive.js b/content/wardrive.js index cb7c4ad..d5b2d0f 100644 --- a/content/wardrive.js +++ b/content/wardrive.js @@ -123,6 +123,7 @@ const state = { distanceUpdateTimer: null, // Timer for updating distance display capturedPingCoords: null, // { lat, lon, accuracy } captured at ping time, used for API post after 7s delay devicePublicKey: null, // Hex string of device's public key (used for capacity check) + disconnectReason: null, // Tracks the reason for disconnection (e.g., "app_down", "capacity_full", "normal") repeaterTracking: { isListening: false, // Whether we're currently listening for echoes sentTimestamp: null, // Timestamp when the ping was sent @@ -1048,6 +1049,7 @@ async function checkCapacity(reason) { if (reason === "connect") { debugError("Failing closed (denying connection) due to API error"); setStatus("WarDriving app is down", STATUS_COLORS.error); + state.disconnectReason = "app_down"; // Track disconnect reason return false; } return true; // Always allow disconnect to proceed @@ -1059,6 +1061,7 @@ async function checkCapacity(reason) { // Handle capacity full vs. allowed cases separately if (data.allowed === false && reason === "connect") { setStatus("WarDriving app has reached capacity", STATUS_COLORS.error); + state.disconnectReason = "capacity_full"; // Track disconnect reason } return data.allowed === true; @@ -1070,6 +1073,7 @@ async function checkCapacity(reason) { if (reason === "connect") { debugError("Failing closed (denying connection) due to network error"); setStatus("WarDriving app is down", STATUS_COLORS.error); + state.disconnectReason = "app_down"; // Track disconnect reason return false; } @@ -2012,7 +2016,8 @@ async function connect() { conn.on("connected", async () => { debugLog("BLE connected event fired"); - setStatus("Connected", STATUS_COLORS.success); + // Keep "Connecting" status visible during the full connection process + // Don't show "Connected" until everything is complete setConnectButton(true); connectBtn.disabled = false; const selfInfo = await conn.getSelfInfo(); @@ -2022,6 +2027,7 @@ async function connect() { if (!selfInfo?.publicKey || selfInfo.publicKey.length !== 32) { debugError("Missing or invalid public key from device", selfInfo?.publicKey); setStatus("Unable to read device public key; try again", STATUS_COLORS.error); + state.disconnectReason = "error"; // Mark as error disconnect // Disconnect after a brief delay to ensure user sees the error message setTimeout(() => { disconnect().catch(err => debugError(`Disconnect after public key error failed: ${err.message}`)); @@ -2050,28 +2056,41 @@ async function connect() { if (!allowed) { debugWarn("Capacity check denied, disconnecting"); // Status message already set by checkCapacity() + // disconnectReason already set by checkCapacity() // Disconnect after a brief delay to ensure user sees the message setTimeout(() => { disconnect().catch(err => debugError(`Disconnect after capacity denial failed: ${err.message}`)); }, 1500); } else { - // Connection complete, set status to Idle - setStatus("Idle", STATUS_COLORS.idle); + // Connection complete - NOW show Connected status + setStatus("Connected", STATUS_COLORS.success); + debugLog("Full connection process completed successfully"); } } catch (e) { debugError(`Channel setup failed: ${e.message}`, e); setStatus(e.message || "Channel setup failed", STATUS_COLORS.error); + state.disconnectReason = "error"; // Mark as error disconnect } }); conn.on("disconnected", () => { debugLog("BLE disconnected event fired"); - setStatus("Disconnected", STATUS_COLORS.error); + + // Only set "Disconnected" status if this wasn't an error disconnect + // If disconnectReason is set, keep the existing error message + if (!state.disconnectReason || state.disconnectReason === "normal") { + setStatus("Disconnected", STATUS_COLORS.error); + } else { + debugLog(`Preserving disconnect status for reason: ${state.disconnectReason}`); + // The error status was already set (e.g., "WarDriving app is down") + } + setConnectButton(false); deviceInfoEl.textContent = "—"; state.connection = null; state.channel = null; state.devicePublicKey = null; // Clear public key + state.disconnectReason = null; // Reset disconnect reason stopAutoPing(true); // Ignore cooldown check on disconnect enableControls(false); updateAutoButton(); @@ -2105,6 +2124,12 @@ async function disconnect() { } connectBtn.disabled = true; + + // Set disconnectReason to "normal" if not already set (for user-initiated disconnects) + if (!state.disconnectReason) { + state.disconnectReason = "normal"; + } + setStatus("Disconnecting", STATUS_COLORS.info); // Release capacity slot if we have a public key @@ -2147,6 +2172,7 @@ async function disconnect() { } catch (e) { debugError(`BLE disconnect failed: ${e.message}`, e); setStatus(e.message || "Disconnect failed", STATUS_COLORS.error); + state.disconnectReason = "error"; // Mark as error disconnect } finally { connectBtn.disabled = false; } From 7e13dcbf401443fe4a1067451aab5ffb3a1bfaef Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 02:46:46 +0000 Subject: [PATCH 11/47] Address code review feedback: improve comments and readability Co-authored-by: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com> --- content/wardrive.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/content/wardrive.js b/content/wardrive.js index d5b2d0f..bf5ec79 100644 --- a/content/wardrive.js +++ b/content/wardrive.js @@ -123,7 +123,7 @@ const state = { distanceUpdateTimer: null, // Timer for updating distance display capturedPingCoords: null, // { lat, lon, accuracy } captured at ping time, used for API post after 7s delay devicePublicKey: null, // Hex string of device's public key (used for capacity check) - disconnectReason: null, // Tracks the reason for disconnection (e.g., "app_down", "capacity_full", "normal") + disconnectReason: null, // Tracks the reason for disconnection (e.g., "app_down", "capacity_full", "error", "normal") repeaterTracking: { isListening: false, // Whether we're currently listening for echoes sentTimestamp: null, // Timestamp when the ping was sent @@ -2062,7 +2062,7 @@ async function connect() { disconnect().catch(err => debugError(`Disconnect after capacity denial failed: ${err.message}`)); }, 1500); } else { - // Connection complete - NOW show Connected status + // Connection complete, show Connected status setStatus("Connected", STATUS_COLORS.success); debugLog("Full connection process completed successfully"); } @@ -2076,13 +2076,12 @@ async function connect() { conn.on("disconnected", () => { debugLog("BLE disconnected event fired"); - // Only set "Disconnected" status if this wasn't an error disconnect - // If disconnectReason is set, keep the existing error message - if (!state.disconnectReason || state.disconnectReason === "normal") { + // Only set "Disconnected" status for normal disconnections + // Preserve error messages (app_down, capacity_full, error) instead of overwriting + if (state.disconnectReason === "normal" || !state.disconnectReason) { setStatus("Disconnected", STATUS_COLORS.error); } else { debugLog(`Preserving disconnect status for reason: ${state.disconnectReason}`); - // The error status was already set (e.g., "WarDriving app is down") } setConnectButton(false); From b2d1863717b6cc5758996525cbd1aba243863d6a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 02:47:41 +0000 Subject: [PATCH 12/47] Improve null checks for disconnectReason Co-authored-by: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com> --- content/wardrive.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/wardrive.js b/content/wardrive.js index bf5ec79..7c7ed63 100644 --- a/content/wardrive.js +++ b/content/wardrive.js @@ -2078,7 +2078,7 @@ async function connect() { // Only set "Disconnected" status for normal disconnections // Preserve error messages (app_down, capacity_full, error) instead of overwriting - if (state.disconnectReason === "normal" || !state.disconnectReason) { + if (state.disconnectReason === "normal" || state.disconnectReason == null) { setStatus("Disconnected", STATUS_COLORS.error); } else { debugLog(`Preserving disconnect status for reason: ${state.disconnectReason}`); @@ -2125,7 +2125,7 @@ async function disconnect() { connectBtn.disabled = true; // Set disconnectReason to "normal" if not already set (for user-initiated disconnects) - if (!state.disconnectReason) { + if (state.disconnectReason == null) { state.disconnectReason = "normal"; } From d280c281ede23d16884d38ae9710562f0360dbcf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 02:48:39 +0000 Subject: [PATCH 13/47] Use strict equality checks for null/undefined Co-authored-by: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com> --- content/wardrive.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/wardrive.js b/content/wardrive.js index 7c7ed63..ea83f0a 100644 --- a/content/wardrive.js +++ b/content/wardrive.js @@ -2078,7 +2078,7 @@ async function connect() { // Only set "Disconnected" status for normal disconnections // Preserve error messages (app_down, capacity_full, error) instead of overwriting - if (state.disconnectReason === "normal" || state.disconnectReason == null) { + if (state.disconnectReason === "normal" || state.disconnectReason === null || state.disconnectReason === undefined) { setStatus("Disconnected", STATUS_COLORS.error); } else { debugLog(`Preserving disconnect status for reason: ${state.disconnectReason}`); @@ -2125,7 +2125,7 @@ async function disconnect() { connectBtn.disabled = true; // Set disconnectReason to "normal" if not already set (for user-initiated disconnects) - if (state.disconnectReason == null) { + if (state.disconnectReason === null || state.disconnectReason === undefined) { state.disconnectReason = "normal"; } From e96948673cb34532110ad4e7bda96740183a7749 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 02:50:25 +0000 Subject: [PATCH 14/47] Standardize disconnect messages with "Disconnected: " prefix Co-authored-by: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com> --- STATUS_MESSAGES.md | 16 ++++++++-------- content/wardrive.js | 9 +++++---- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/STATUS_MESSAGES.md b/STATUS_MESSAGES.md index 205e715..4bcb505 100644 --- a/STATUS_MESSAGES.md +++ b/STATUS_MESSAGES.md @@ -91,23 +91,23 @@ Status messages follow these consistent conventions: - **Context**: When connecting to device and checking if a wardriving slot is available - **Minimum Visibility**: 500ms minimum enforced (or until API response received) -#### WarDriving app has reached capacity -- **Message**: `"WarDriving app has reached capacity"` +#### Disconnected: WarDriving app has reached capacity +- **Message**: `"Disconnected: WarDriving app has reached capacity"` - **Color**: Red (error) - **Used in**: `checkCapacity()`, `postToMeshMapperAPI()` -- **Source**: `content/wardrive.js:1061`, `content/wardrive.js:1116` +- **Source**: `content/wardrive.js:1063`, `content/wardrive.js:1120` - **Context**: Capacity check API denies slot on connect (returns allowed=false), or wardriving API returns allowed=false during active session - **Minimum Visibility**: N/A (error state persists; message is preserved during automatic disconnect) -- **Notes**: Displayed when the API successfully responds but indicates capacity is full. When this error occurs during connection, the automatic disconnect flow preserves this status message instead of showing "Disconnected" +- **Notes**: Displayed when the API successfully responds but indicates capacity is full. When this error occurs during connection, the automatic disconnect flow preserves this status message instead of showing "Disconnected". Message format standardized with "Disconnected: " prefix to clearly indicate disconnect state. -#### WarDriving app is down -- **Message**: `"WarDriving app is down"` +#### Disconnected: WarDriving app is down +- **Message**: `"Disconnected: WarDriving app is down"` - **Color**: Red (error) - **Used in**: `checkCapacity()` -- **Source**: `content/wardrive.js:1050`, `content/wardrive.js:1072` +- **Source**: `content/wardrive.js:1050`, `content/wardrive.js:1074` - **Context**: Capacity check API returns error status or network is unreachable during connect - **Minimum Visibility**: N/A (error state persists; message is preserved during automatic disconnect) -- **Notes**: Implements fail-closed policy - connection is denied if API fails or is unreachable. When this error occurs, the automatic disconnect flow preserves this status message instead of showing "Disconnected" +- **Notes**: Implements fail-closed policy - connection is denied if API fails or is unreachable. When this error occurs, the automatic disconnect flow preserves this status message instead of showing "Disconnected". Message format standardized with "Disconnected: " prefix to clearly indicate disconnect state. #### Unable to read device public key; try again - **Message**: `"Unable to read device public key; try again"` diff --git a/content/wardrive.js b/content/wardrive.js index ea83f0a..9559e6c 100644 --- a/content/wardrive.js +++ b/content/wardrive.js @@ -1048,7 +1048,7 @@ async function checkCapacity(reason) { // Fail closed on network errors for connect if (reason === "connect") { debugError("Failing closed (denying connection) due to API error"); - setStatus("WarDriving app is down", STATUS_COLORS.error); + setStatus("Disconnected: WarDriving app is down", STATUS_COLORS.error); state.disconnectReason = "app_down"; // Track disconnect reason return false; } @@ -1060,7 +1060,7 @@ async function checkCapacity(reason) { // Handle capacity full vs. allowed cases separately if (data.allowed === false && reason === "connect") { - setStatus("WarDriving app has reached capacity", STATUS_COLORS.error); + setStatus("Disconnected: WarDriving app has reached capacity", STATUS_COLORS.error); state.disconnectReason = "capacity_full"; // Track disconnect reason } @@ -1072,7 +1072,7 @@ async function checkCapacity(reason) { // Fail closed on network errors for connect if (reason === "connect") { debugError("Failing closed (denying connection) due to network error"); - setStatus("WarDriving app is down", STATUS_COLORS.error); + setStatus("Disconnected: WarDriving app is down", STATUS_COLORS.error); state.disconnectReason = "app_down"; // Track disconnect reason return false; } @@ -1117,7 +1117,8 @@ async function postToMeshMapperAPI(lat, lon, heardRepeats) { const data = await response.json(); if (data.allowed === false) { debugWarn("MeshMapper API returned allowed=false, disconnecting"); - setStatus("WarDriving app has reached capacity", STATUS_COLORS.error); + setStatus("Disconnected: WarDriving app has reached capacity", STATUS_COLORS.error); + state.disconnectReason = "capacity_full"; // Track disconnect reason // Disconnect after a brief delay to ensure user sees the message setTimeout(() => { disconnect().catch(err => debugError(`Disconnect after capacity denial failed: ${err.message}`)); From 700911864a5e8ba30bad2680ee8d1651cff0eb3f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 03:19:41 +0000 Subject: [PATCH 15/47] Initial plan From 3b9ebc9464a6564c0b7c0b632790956e933a3ace Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 03:21:31 +0000 Subject: [PATCH 16/47] Initial plan From cc1b89927bb52861a868a7399a4ccd42ca960ff7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 03:23:13 +0000 Subject: [PATCH 17/47] Implement WarDriving slot revocation check on API responses - Modified postToMeshMapperAPI() to check 'allowed' field on every POST - Added debug logging when allowed === true (device still has active slot) - Changed disconnect behavior when allowed === false to use new status message - Updated status message from 'capacity' to 'slot has been revoked' - Added new disconnectReason 'slot_revoked' for tracking - Updated STATUS_MESSAGES.md with new status message documentation Co-authored-by: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com> --- STATUS_MESSAGES.md | 21 +++++++++++++++------ content/wardrive.js | 10 ++++++---- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/STATUS_MESSAGES.md b/STATUS_MESSAGES.md index 4bcb505..3d28582 100644 --- a/STATUS_MESSAGES.md +++ b/STATUS_MESSAGES.md @@ -94,11 +94,20 @@ Status messages follow these consistent conventions: #### Disconnected: WarDriving app has reached capacity - **Message**: `"Disconnected: WarDriving app has reached capacity"` - **Color**: Red (error) -- **Used in**: `checkCapacity()`, `postToMeshMapperAPI()` -- **Source**: `content/wardrive.js:1063`, `content/wardrive.js:1120` -- **Context**: Capacity check API denies slot on connect (returns allowed=false), or wardriving API returns allowed=false during active session +- **Used in**: `checkCapacity()` +- **Source**: `content/wardrive.js:1063` +- **Context**: Capacity check API denies slot on connect (returns allowed=false) +- **Minimum Visibility**: N/A (error state persists; message is preserved during automatic disconnect) +- **Notes**: Displayed when the API successfully responds but indicates capacity is full at connection time. When this error occurs during connection, the automatic disconnect flow preserves this status message instead of showing "Disconnected". Message format standardized with "Disconnected: " prefix to clearly indicate disconnect state. + +#### Disconnected: WarDriving slot has been revoked +- **Message**: `"Disconnected: WarDriving slot has been revoked"` +- **Color**: Red (error) +- **Used in**: `postToMeshMapperAPI()` +- **Source**: `content/wardrive.js:1120` +- **Context**: WarDriving API returns allowed=false during an active session (every POST to wardriving-api.php) - **Minimum Visibility**: N/A (error state persists; message is preserved during automatic disconnect) -- **Notes**: Displayed when the API successfully responds but indicates capacity is full. When this error occurs during connection, the automatic disconnect flow preserves this status message instead of showing "Disconnected". Message format standardized with "Disconnected: " prefix to clearly indicate disconnect state. +- **Notes**: Displayed when the API successfully responds but indicates the device's WarDriving slot has been revoked. This check happens on every POST to the API endpoint. When this error occurs, the device is immediately disconnected and the automatic disconnect flow preserves this status message instead of showing "Disconnected". Message format standardized with "Disconnected: " prefix to clearly indicate disconnect state. #### Disconnected: WarDriving app is down - **Message**: `"Disconnected: WarDriving app is down"` @@ -372,9 +381,9 @@ Result: "Message A" (visible 500ms) → "Message C" ## Summary -**Total Status Messages**: 30 unique message patterns +**Total Status Messages**: 31 unique message patterns - **Connection**: 7 messages -- **Capacity Check**: 4 messages (1 deprecated) +- **Capacity Check**: 5 messages (1 deprecated, 1 new for slot revocation) - **Ping Operation**: 6 messages (consolidated "Ping sent" for both manual and auto) - **GPS**: 2 messages - **Countdown Timers**: 6 message patterns (with dynamic countdown values) diff --git a/content/wardrive.js b/content/wardrive.js index 9559e6c..7b139bf 100644 --- a/content/wardrive.js +++ b/content/wardrive.js @@ -1116,13 +1116,15 @@ async function postToMeshMapperAPI(lat, lon, heardRepeats) { try { const data = await response.json(); if (data.allowed === false) { - debugWarn("MeshMapper API returned allowed=false, disconnecting"); - setStatus("Disconnected: WarDriving app has reached capacity", STATUS_COLORS.error); - state.disconnectReason = "capacity_full"; // Track disconnect reason + debugWarn("MeshMapper API returned allowed=false, WarDriving slot has been revoked, disconnecting"); + setStatus("Disconnected: WarDriving slot has been revoked", STATUS_COLORS.error); + state.disconnectReason = "slot_revoked"; // Track disconnect reason // Disconnect after a brief delay to ensure user sees the message setTimeout(() => { - disconnect().catch(err => debugError(`Disconnect after capacity denial failed: ${err.message}`)); + disconnect().catch(err => debugError(`Disconnect after slot revocation failed: ${err.message}`)); }, 1500); + } else if (data.allowed === true) { + debugLog("MeshMapper API allowed check passed: device still has an active WarDriving slot"); } } catch (parseError) { debugWarn(`Failed to parse MeshMapper API response: ${parseError.message}`); From 3d33c9e4956e053f2f85548d56b1f9c99c7f1d2c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 03:34:41 +0000 Subject: [PATCH 18/47] Initial plan From 3e5ad484a91d58cfb91c8f2c579e26a2b2741675 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 03:38:45 +0000 Subject: [PATCH 19/47] Fix slot revocation check to always parse API response Co-authored-by: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com> --- STATUS_MESSAGES.md | 4 ++-- content/wardrive.js | 52 ++++++++++++++++++++++++++++----------------- 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/STATUS_MESSAGES.md b/STATUS_MESSAGES.md index 3d28582..bf14e1c 100644 --- a/STATUS_MESSAGES.md +++ b/STATUS_MESSAGES.md @@ -104,10 +104,10 @@ Status messages follow these consistent conventions: - **Message**: `"Disconnected: WarDriving slot has been revoked"` - **Color**: Red (error) - **Used in**: `postToMeshMapperAPI()` -- **Source**: `content/wardrive.js:1120` +- **Source**: `content/wardrive.js:1122` - **Context**: WarDriving API returns allowed=false during an active session (every POST to wardriving-api.php) - **Minimum Visibility**: N/A (error state persists; message is preserved during automatic disconnect) -- **Notes**: Displayed when the API successfully responds but indicates the device's WarDriving slot has been revoked. This check happens on every POST to the API endpoint. When this error occurs, the device is immediately disconnected and the automatic disconnect flow preserves this status message instead of showing "Disconnected". Message format standardized with "Disconnected: " prefix to clearly indicate disconnect state. +- **Notes**: Displayed when the API responds with `allowed=false`, indicating the device's WarDriving slot has been revoked. This check happens on every POST to the API endpoint, regardless of HTTP status code. Critical messages are logged to console even without debug mode enabled. When this error occurs, the device is immediately disconnected and the automatic disconnect flow preserves this status message instead of showing "Disconnected". Message format standardized with "Disconnected: " prefix to clearly indicate disconnect state. #### Disconnected: WarDriving app is down - **Message**: `"Disconnected: WarDriving app is down"` diff --git a/content/wardrive.js b/content/wardrive.js index 7b139bf..cfbca29 100644 --- a/content/wardrive.js +++ b/content/wardrive.js @@ -1107,29 +1107,43 @@ async function postToMeshMapperAPI(lat, lon, heardRepeats) { body: JSON.stringify(payload) }); + debugLog(`MeshMapper API response status: ${response.status}`); + + // Always try to parse the response body to check for slot revocation + // regardless of HTTP status code + try { + const data = await response.json(); + debugLog(`MeshMapper API response data: ${JSON.stringify(data)}`); + + // Check if slot has been revoked (critical check, always log to console) + if (data.allowed === false) { + console.warn("[SLOT REVOKED] MeshMapper API returned allowed=false, WarDriving slot has been revoked, disconnecting"); + debugWarn("MeshMapper API returned allowed=false, WarDriving slot has been revoked, disconnecting"); + setStatus("Disconnected: WarDriving slot has been revoked", STATUS_COLORS.error); + state.disconnectReason = "slot_revoked"; // Track disconnect reason + // Disconnect after a brief delay to ensure user sees the message + setTimeout(() => { + disconnect().catch(err => { + console.error("[SLOT REVOKED] Disconnect after slot revocation failed:", err.message); + debugError(`Disconnect after slot revocation failed: ${err.message}`); + }); + }, 1500); + return; // Exit early after slot revocation + } else if (data.allowed === true) { + debugLog("MeshMapper API allowed check passed: device still has an active WarDriving slot"); + } else { + debugWarn(`MeshMapper API response missing 'allowed' field: ${JSON.stringify(data)}`); + } + } catch (parseError) { + debugWarn(`Failed to parse MeshMapper API response: ${parseError.message}`); + console.warn("[API WARNING] Failed to parse MeshMapper API response:", parseError.message); + // Continue operation if we can't parse the response + } + if (!response.ok) { debugWarn(`MeshMapper API returned error status ${response.status}`); } else { debugLog(`MeshMapper API post successful (status ${response.status})`); - - // Parse response and check if we're allowed to continue - try { - const data = await response.json(); - if (data.allowed === false) { - debugWarn("MeshMapper API returned allowed=false, WarDriving slot has been revoked, disconnecting"); - setStatus("Disconnected: WarDriving slot has been revoked", STATUS_COLORS.error); - state.disconnectReason = "slot_revoked"; // Track disconnect reason - // Disconnect after a brief delay to ensure user sees the message - setTimeout(() => { - disconnect().catch(err => debugError(`Disconnect after slot revocation failed: ${err.message}`)); - }, 1500); - } else if (data.allowed === true) { - debugLog("MeshMapper API allowed check passed: device still has an active WarDriving slot"); - } - } catch (parseError) { - debugWarn(`Failed to parse MeshMapper API response: ${parseError.message}`); - // Continue operation if we can't parse the response - } } } catch (error) { // Log error but don't fail the ping From 79f57d647053435b1e01fd996f633e6dd13a8c89 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 03:40:03 +0000 Subject: [PATCH 20/47] Remove duplicate logging, keep console logging for critical events Co-authored-by: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com> --- content/wardrive.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/content/wardrive.js b/content/wardrive.js index cfbca29..02e2da1 100644 --- a/content/wardrive.js +++ b/content/wardrive.js @@ -1118,14 +1118,12 @@ async function postToMeshMapperAPI(lat, lon, heardRepeats) { // Check if slot has been revoked (critical check, always log to console) if (data.allowed === false) { console.warn("[SLOT REVOKED] MeshMapper API returned allowed=false, WarDriving slot has been revoked, disconnecting"); - debugWarn("MeshMapper API returned allowed=false, WarDriving slot has been revoked, disconnecting"); setStatus("Disconnected: WarDriving slot has been revoked", STATUS_COLORS.error); state.disconnectReason = "slot_revoked"; // Track disconnect reason // Disconnect after a brief delay to ensure user sees the message setTimeout(() => { disconnect().catch(err => { console.error("[SLOT REVOKED] Disconnect after slot revocation failed:", err.message); - debugError(`Disconnect after slot revocation failed: ${err.message}`); }); }, 1500); return; // Exit early after slot revocation From 09cdcd76ea8a7da4a4e1a3dfa3abd60cda9c2abf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 03:42:26 +0000 Subject: [PATCH 21/47] Use debug logging consistently, only show logs with ?debug=true Co-authored-by: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com> --- STATUS_MESSAGES.md | 4 ++-- content/wardrive.js | 9 +++------ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/STATUS_MESSAGES.md b/STATUS_MESSAGES.md index bf14e1c..cbf7abd 100644 --- a/STATUS_MESSAGES.md +++ b/STATUS_MESSAGES.md @@ -104,10 +104,10 @@ Status messages follow these consistent conventions: - **Message**: `"Disconnected: WarDriving slot has been revoked"` - **Color**: Red (error) - **Used in**: `postToMeshMapperAPI()` -- **Source**: `content/wardrive.js:1122` +- **Source**: `content/wardrive.js:1121` - **Context**: WarDriving API returns allowed=false during an active session (every POST to wardriving-api.php) - **Minimum Visibility**: N/A (error state persists; message is preserved during automatic disconnect) -- **Notes**: Displayed when the API responds with `allowed=false`, indicating the device's WarDriving slot has been revoked. This check happens on every POST to the API endpoint, regardless of HTTP status code. Critical messages are logged to console even without debug mode enabled. When this error occurs, the device is immediately disconnected and the automatic disconnect flow preserves this status message instead of showing "Disconnected". Message format standardized with "Disconnected: " prefix to clearly indicate disconnect state. +- **Notes**: Displayed when the API responds with `allowed=false`, indicating the device's WarDriving slot has been revoked. This check happens on every POST to the API endpoint, regardless of HTTP status code. Debug messages are logged when `?debug=true` is in the URL. When this error occurs, the device is immediately disconnected and the automatic disconnect flow preserves this status message instead of showing "Disconnected". Message format standardized with "Disconnected: " prefix to clearly indicate disconnect state. #### Disconnected: WarDriving app is down - **Message**: `"Disconnected: WarDriving app is down"` diff --git a/content/wardrive.js b/content/wardrive.js index 02e2da1..9215568 100644 --- a/content/wardrive.js +++ b/content/wardrive.js @@ -1115,16 +1115,14 @@ async function postToMeshMapperAPI(lat, lon, heardRepeats) { const data = await response.json(); debugLog(`MeshMapper API response data: ${JSON.stringify(data)}`); - // Check if slot has been revoked (critical check, always log to console) + // Check if slot has been revoked if (data.allowed === false) { - console.warn("[SLOT REVOKED] MeshMapper API returned allowed=false, WarDriving slot has been revoked, disconnecting"); + debugWarn("MeshMapper API returned allowed=false, WarDriving slot has been revoked, disconnecting"); setStatus("Disconnected: WarDriving slot has been revoked", STATUS_COLORS.error); state.disconnectReason = "slot_revoked"; // Track disconnect reason // Disconnect after a brief delay to ensure user sees the message setTimeout(() => { - disconnect().catch(err => { - console.error("[SLOT REVOKED] Disconnect after slot revocation failed:", err.message); - }); + disconnect().catch(err => debugError(`Disconnect after slot revocation failed: ${err.message}`)); }, 1500); return; // Exit early after slot revocation } else if (data.allowed === true) { @@ -1134,7 +1132,6 @@ async function postToMeshMapperAPI(lat, lon, heardRepeats) { } } catch (parseError) { debugWarn(`Failed to parse MeshMapper API response: ${parseError.message}`); - console.warn("[API WARNING] Failed to parse MeshMapper API response:", parseError.message); // Continue operation if we can't parse the response } From b3140c5c408c22caba654369109bf3e47544fba1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 03:45:51 +0000 Subject: [PATCH 22/47] Add complete application workflow documentation Co-authored-by: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com> --- docs/application-workflow.md | 498 +++++++++++++++++++++++++++++++++++ 1 file changed, 498 insertions(+) create mode 100644 docs/application-workflow.md diff --git a/docs/application-workflow.md b/docs/application-workflow.md new file mode 100644 index 0000000..6824cf8 --- /dev/null +++ b/docs/application-workflow.md @@ -0,0 +1,498 @@ +# Application Workflow Documentation + +## Table of Contents +- [Overview](#overview) + - [Connection Overview](#connection-overview) + - [Disconnection Overview](#disconnection-overview) +- [Connection Workflow](#connection-workflow) +- [Disconnection Workflow](#disconnection-workflow) +- [Workflow Diagrams](#workflow-diagrams) +- [Code References](#code-references) +- [Edge Cases and Gotchas](#edge-cases-and-gotchas) + +## Overview + +### Connection Overview + +**What "Connect" Means:** +- Establishes a Web Bluetooth (BLE) connection to a MeshCore companion device +- Configures the device for wardriving operations +- Creates or finds the `#wardriving` channel for sending GPS pings +- Acquires an API slot from the MeshMapper backend for capacity management +- Initializes GPS tracking for location services +- Enables the app to send wardrive pings to the mesh network + +**Connected State Enables:** +- Manual ping transmission (via "Send Ping" button) +- Automatic ping transmission (via "Start Auto Ping" with configurable intervals: 15s/30s/60s) +- GPS coordinate tracking and display +- Real-time repeater echo detection +- Integration with MeshMapper API for coverage mapping +- Session ping history logging + +### Disconnection Overview + +**What "Disconnect" Means:** +- Cleanly terminates the BLE connection to the MeshCore device +- Releases the API slot back to the MeshMapper backend +- Deletes the `#wardriving` channel from the device (cleanup) +- Stops all running timers and operations (auto-ping, GPS watch, wake locks) +- Clears all connection state and resets the UI +- Returns the application to idle state, ready for a new connection + +**Expected App State After Disconnect:** +- Status: "Disconnected" (red) +- All controls disabled except "Connect" button +- GPS tracking stopped +- Auto-ping mode disabled +- All timers cleared +- Connection state reset +- Ready to initiate a new connection + +## Connection Workflow + +### Connection Steps (High-Level) + +1. **User Initiates** → User clicks "Connect" button +2. **Device Selection** → Browser shows BLE device picker +3. **BLE GATT Connection** → Establishes GATT connection to device +4. **Protocol Handshake** → Exchanges protocol version +5. **Device Info** → Retrieves device name, public key, settings +6. **Time Sync** → Synchronizes device clock +7. **Channel Setup** → Creates/finds #wardriving channel +8. **GPS Init** → Starts GPS tracking +9. **Capacity Check** → Acquires API slot from MeshMapper +10. **Connected** → Enables all controls, ready for wardriving + +### Detailed Connection Steps + +See `content/wardrive.js` lines 2002-2118 for the main `connect()` function. + +**Key Entry Point:** +```javascript +connectBtn.addEventListener("click", async () => { + if (state.connection) { + await disconnect(); + } else { + await connect(); + } +}); +``` + +**Connection Sequence:** + +1. **Validate Web Bluetooth Support** + - Checks `navigator.bluetooth` exists + - Alerts user if not supported + - Fails fast if unavailable + +2. **Open BLE Connection** + - Calls `WebBleConnection.open()` (web_ble_connection.js:15-41) + - Shows browser's native device picker + - Filters for MeshCore BLE service UUID + - User selects device or cancels + +3. **Initialize BLE** + - Connects to GATT server + - Discovers service and characteristics (RX/TX) + - Starts notifications on TX characteristic + - Sets up frame listener for incoming data + - Fires "connected" event + +4. **Device Query** + - Sends protocol version query + - Non-critical, errors ignored + +5. **Get Device Info** + - Retrieves device name, public key (32 bytes), settings + - **CRITICAL**: Validates public key length + - Converts to hex string + - Stores in `state.devicePublicKey` + - Updates UI with device name + - Changes button to "Disconnect" (red) + +6. **Sync Device Time** + - Sends current Unix timestamp + - Device updates its clock + - Optional, errors ignored + +7. **Setup Channel** + - Searches for existing `#wardriving` channel + - If not found, creates new channel: + - Finds empty channel slot + - Derives channel key: `SHA-256(#wardriving).slice(0, 16)` + - Sends setChannel command + - Stores channel object in `state.channel` + - Updates UI: "#wardriving (CH:X)" + +8. **Initialize GPS** + - Requests location permission + - Gets initial GPS position (30s timeout) + - Starts continuous GPS watch + - Starts GPS age updater (1s interval) + - Starts distance updater (3s interval) + - Updates UI with coordinates and accuracy + - Refreshes coverage map if accuracy < 100m + +9. **Check Capacity** + - POSTs to MeshMapper API: + ```json + { + "key": "API_KEY", + "public_key": "device_hex_key", + "who": "device_name", + "reason": "connect" + } + ``` + - If `allowed: false` → disconnects with error + - If API error → disconnects (fail-closed) + +10. **Connection Complete** + - Sets status to "Connected" (green) + - Enables all UI controls + - Ready for wardriving operations + +## Disconnection Workflow + +### Disconnection Steps (High-Level) + +1. **Disconnect Trigger** → User clicks "Disconnect" or error occurs +2. **Status Update** → Shows "Disconnecting" +3. **Capacity Release** → Returns API slot to MeshMapper +4. **Channel Deletion** → Removes #wardriving channel from device +5. **BLE Disconnect** → Closes GATT connection +6. **Cleanup** → Stops timers, GPS, wake locks +7. **State Reset** → Clears all connection state +8. **Disconnected** → Returns to idle state + +### Detailed Disconnection Steps + +See `content/wardrive.js` lines 2119-2179 for the main `disconnect()` function. + +**Disconnect Triggers:** +- User clicks "Disconnect" button +- Capacity denial during connect +- Public key validation failure +- Channel setup failure +- BLE connection lost (device out of range) + +**Disconnection Sequence:** + +1. **Disable Button** + - Prevents duplicate disconnect requests + +2. **Set Disconnect Reason** + - "normal" - user-initiated + - "capacity_full" - MeshMapper full + - "app_down" - API unavailable + - "error" - validation/setup failure + +3. **Update Status** + - Sets status to "Disconnecting" (blue) + +4. **Release Capacity** + - POSTs to MeshMapper API with `reason: "disconnect"` + - **Fail-open**: errors ignored, always proceeds + +5. **Delete Channel** + - Sends `setChannel(idx, "", zeros)` to clear slot + - **Fail-open**: errors ignored, always proceeds + +6. **Close BLE** + - Tries `connection.close()` + - Falls back to `connection.disconnect()` + - Last resort: `device.gatt.disconnect()` + - Triggers "gattserverdisconnected" event + +7. **Disconnected Event Handler** + - Fires on BLE disconnect + - Runs comprehensive cleanup: + - Stops auto-ping mode + - Clears auto-ping timer + - Stops GPS watch + - Stops GPS age updater + - Stops distance updater + - Stops repeater tracking + - Clears all timers (see `cleanupAllTimers()`) + - Releases wake lock + - Clears connection state + - Clears device public key + +8. **UI Cleanup** + - Disables all controls except "Connect" + - Clears device info display + - Clears GPS display + - Clears distance display + - Changes button to "Connect" (green) + +9. **State Reset** + - `state.connection = null` + - `state.channel = null` + - `state.lastFix = null` + - `state.lastSuccessfulPingLocation = null` + - `state.gpsState = "idle"` + +10. **Disconnected Complete** + - Status: "Disconnected" (red) or error message + - All resources released + - Ready for new connection + +## Workflow Diagrams + +### Connection Sequence + +```mermaid +sequenceDiagram + actor User + participant UI + participant App as wardrive.js + participant BLE as WebBleConnection + participant Device as MeshCore Device + participant GPS + participant API as MeshMapper API + + User->>UI: Click "Connect" + UI->>App: connect() + App->>BLE: WebBleConnection.open() + BLE->>User: Show device picker + User->>BLE: Select device + BLE->>Device: GATT Connect + Device-->>BLE: Connected + BLE->>Device: Discover services + BLE->>App: Fire "connected" + + App->>Device: DeviceQuery + Device-->>App: DeviceInfo + + App->>Device: getSelfInfo() + Device-->>App: SelfInfo + public key + App->>App: Validate & store key + + App->>Device: syncDeviceTime() + Device-->>App: OK + + App->>Device: findChannel("#wardriving") + alt Not found + App->>Device: createChannel() + end + + App->>GPS: getCurrentPosition() + GPS-->>App: Position + App->>GPS: watchPosition() + + App->>API: checkCapacity("connect") + alt Allowed + API-->>App: {allowed: true} + App->>UI: Status "Connected" + else Denied + API-->>App: {allowed: false} + App->>App: disconnect() + end +``` + +### Disconnection Sequence + +```mermaid +sequenceDiagram + actor User + participant UI + participant App as wardrive.js + participant BLE as WebBleConnection + participant Device + participant GPS + participant API as MeshMapper API + + User->>UI: Click "Disconnect" + UI->>App: disconnect() + App->>UI: Status "Disconnecting" + + App->>API: checkCapacity("disconnect") + Note over API: Errors ignored + + App->>Device: deleteChannel() + Note over Device: Errors ignored + + App->>BLE: close() + BLE->>Device: GATT Disconnect + Device-->>BLE: Disconnected + BLE->>App: Fire "disconnected" + + App->>App: stopAutoPing() + App->>GPS: clearWatch() + App->>App: cleanupAllTimers() + App->>App: Reset state + + App->>UI: Status "Disconnected" + App->>UI: Disable controls + App->>UI: Button "Connect" +``` + +### State Machine + +```mermaid +stateDiagram-v2 + [*] --> Disconnected + + Disconnected --> Connecting: User clicks Connect + Connecting --> DeviceSelection: BLE available + Connecting --> Disconnected: BLE not supported + + DeviceSelection --> BLEConnection: Device selected + DeviceSelection --> Disconnected: User cancels + + BLEConnection --> DeviceInfo: GATT connected + BLEConnection --> Disconnected: GATT fails + + DeviceInfo --> ChannelSetup: Info valid + DeviceInfo --> Disconnecting: Invalid key + + ChannelSetup --> GPSInit: Channel ready + ChannelSetup --> Disconnecting: Setup fails + + GPSInit --> CapacityCheck: GPS started + + CapacityCheck --> Connected: Slot acquired + CapacityCheck --> Disconnecting: Denied + + Connected --> Disconnecting: User disconnect + Connected --> Disconnecting: BLE lost + + Disconnecting --> Disconnected: Cleanup complete +``` + +## Code References + +### Connect Entry Points +- **Main function**: `wardrive.js:connect()` (lines 2002-2118) +- **Button listener**: `wardrive.js` line 2207 +- **WebBLE open**: `web_ble_connection.js:WebBleConnection.open()` (lines 15-41) +- **BLE init**: `web_ble_connection.js:init()` (lines 43-77) + +### Disconnect Entry Points +- **Main function**: `wardrive.js:disconnect()` (lines 2119-2179) +- **Button listener**: `wardrive.js` line 2207 (same button, checks state) +- **Auto disconnect**: triggered by capacity/validation failures +- **BLE event**: `gattserverdisconnected` (web_ble_connection.js:46) + +### Key Connection Functions +- **Channel setup**: `wardrive.js:ensureChannel()` (lines 941-971) +- **Channel creation**: `wardrive.js:createWardriveChannel()` (lines 899-939) +- **Key derivation**: `wardrive.js:deriveChannelKey()` (lines 847-896) +- **GPS init**: `wardrive.js:primeGpsOnce()` (lines 804-842) +- **GPS watch**: `wardrive.js:startGeoWatch()` (lines 751-792) +- **Capacity check**: `wardrive.js:checkCapacity()` (lines 1018-1082) + +### Key Disconnection Functions +- **Timer cleanup**: `wardrive.js:cleanupAllTimers()` (lines 427-460) +- **Auto-ping stop**: `wardrive.js:stopAutoPing()` (lines 1904-1934) +- **GPS watch stop**: `wardrive.js:stopGeoWatch()` (lines 793-803) +- **Repeater stop**: `wardrive.js:stopRepeaterTracking()` (lines 1506-1544) +- **Channel delete**: `connection.js:deleteChannel()` (lines 1909-1911) + +### State Management +- **Global state**: `wardrive.js` lines 102-136 (`const state = {...}`) +- **Status management**: `wardrive.js:setStatus()` (lines 165-225) +- **Button state**: `wardrive.js:setConnectButton()` (lines 495-518) +- **Control state**: `wardrive.js:enableControls()` (lines 462-466) + +### Transport Implementation +- **WebBLE class**: `web_ble_connection.js` (lines 4-106) +- **Base connection**: `connection.js` (lines 9-2218) +- **Event emitter**: `events.js` +- **Constants**: `constants.js` + +## Edge Cases and Gotchas + +### Connect While Connected +- Button acts as toggle +- If connected, triggers disconnect +- No duplicate connections possible + +### Disconnect While Connecting +- Button disabled during connect +- Only auto-disconnects possible (capacity/validation) +- GATT disconnect triggers normal cleanup + +### Browser Refresh +- All state lost +- BLE connection dropped +- Session ping log cleared +- User must reconnect +- No auto-reconnect + +### Network Loss +- **BLE loss**: auto-disconnect via `gattserverdisconnected` +- **API timeout (connect)**: fail-closed, deny connection +- **API timeout (disconnect)**: fail-open, allow disconnect +- **Ping API timeout**: fail-open, ping considered sent + +### Reconnect Behavior +- No automatic reconnect +- User must manually click "Connect" +- Full connection sequence runs +- New capacity slot acquired +- Previous session data lost + +### Resource Leaks +- Comprehensive cleanup on disconnect +- All timers tracked and cleared +- All event listeners removed +- GPS watch cleared +- Wake lock released +- **No known leaks** ✅ + +### Capacity Management +- **Slot exhaustion**: connect denied, user waits +- **Release failure**: disconnect proceeds anyway +- **API downtime**: connect fails (fail-closed) +- **Orphaned slots**: rely on server-side timeout + +### Channel Management +- **No empty slots**: error shown, pings unavailable +- **Channel exists**: reuses existing, no warning +- **Delete failure**: logged, disconnect proceeds +- **Conflict risk**: ensure MeshCore app disconnected first + +### GPS Edge Cases +- **Permission denied**: error shown, pings still work +- **Low accuracy**: allowed, map refresh skipped +- **Timeout**: watch continues, may skip auto-pings +- **Signal loss**: watch polls, old data check enforced + +### Auto-Ping Interactions +- **Manual during auto**: auto pauses, resumes after +- **7s cooldown**: prevents rapid-fire pings +- **Page hidden**: auto stops, must restart manually +- **Cooldown bypass**: only on disconnect + +### Error Recovery +- **Connect fails**: button re-enabled, user retries +- **Ping fails**: auto continues, no retry +- **API fails (connect)**: fail-closed, user retries +- **API fails (disconnect)**: fail-open, always succeeds + +### State Consistency +- Single state object (`state`) +- No partial states +- Clear transitions +- Error → Disconnected +- Recovery always possible + +## Summary + +MeshCore-GOME-WarDriver implements a robust Web Bluetooth wardriving application with clear connection/disconnection workflows: + +**Key Design Principles:** +1. **Fail-Closed on Connect**: API errors deny connection +2. **Fail-Open on Disconnect**: Always proceed regardless of errors +3. **Comprehensive Cleanup**: All resources explicitly released +4. **Clear State Machine**: No ambiguous states +5. **User Transparency**: Status messages at every step + +**Connection:** BLE → Device Info → Channel Setup → GPS → Capacity Check → Connected + +**Disconnection:** Capacity Release → Channel Delete → BLE Close → Full Cleanup → Disconnected + +**Debug Mode:** Add `?debug=true` to URL for detailed logging + +The workflow prioritizes reliability, clear error messages, and complete resource cleanup on every disconnect. From fa4681c79375bd5f889bb27f08768f1c0a44579a Mon Sep 17 00:00:00 2001 From: MrAlders0n Date: Fri, 19 Dec 2025 22:52:06 -0500 Subject: [PATCH 23/47] Add comprehensive documentation for application workflow and status messages --- docs/{application-workflow.md => App-Connection-Workflow.md} | 0 STATUS_MESSAGES.md => docs/STATUS_MESSAGES.md | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename docs/{application-workflow.md => App-Connection-Workflow.md} (100%) rename STATUS_MESSAGES.md => docs/STATUS_MESSAGES.md (100%) diff --git a/docs/application-workflow.md b/docs/App-Connection-Workflow.md similarity index 100% rename from docs/application-workflow.md rename to docs/App-Connection-Workflow.md diff --git a/STATUS_MESSAGES.md b/docs/STATUS_MESSAGES.md similarity index 100% rename from STATUS_MESSAGES.md rename to docs/STATUS_MESSAGES.md From 1c6a2ea989e59a40056483765d71ed2bdcd4c3ea Mon Sep 17 00:00:00 2001 From: MrAlders0n Date: Fri, 19 Dec 2025 22:55:58 -0500 Subject: [PATCH 24/47] Add detailed application connection and disconnection workflow documentation --- docs/{App-Connection-Workflow.md => CONNECTION_WORKFLOW.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/{App-Connection-Workflow.md => CONNECTION_WORKFLOW.md} (100%) diff --git a/docs/App-Connection-Workflow.md b/docs/CONNECTION_WORKFLOW.md similarity index 100% rename from docs/App-Connection-Workflow.md rename to docs/CONNECTION_WORKFLOW.md From 00f43468fb096071bb18e9210de8c300a5506b31 Mon Sep 17 00:00:00 2001 From: MrAlders0n Date: Fri, 19 Dec 2025 23:01:04 -0500 Subject: [PATCH 25/47] Update development guidelines to include revised app connection flow and logging standards --- docs/DEVELOPMENT_REQUIREMENTS.md | 76 ++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 docs/DEVELOPMENT_REQUIREMENTS.md diff --git a/docs/DEVELOPMENT_REQUIREMENTS.md b/docs/DEVELOPMENT_REQUIREMENTS.md new file mode 100644 index 0000000..cf53504 --- /dev/null +++ b/docs/DEVELOPMENT_REQUIREMENTS.md @@ -0,0 +1,76 @@ +# MeshCore GOME WarDriver - Development Guidelines + +## Overview +This document defines the coding standards and requirements for all changes to the MeshCore GOME WarDriver repository. AI agents and contributors must follow these guidelines for every modification. + +--- + +## Code Style & Standards + +### Debug Logging +- **ALWAYS** include debug console logging for significant operations +- Use the existing debug helper functions: + - `debugLog(message, ...args)` - For general debug information + - `debugWarn(message, ... args)` - For warning conditions + - `debugError(message, ... args)` - For error conditions +- Debug logging is controlled by the `DEBUG_ENABLED` flag (URL parameter `? debug=true`) +- Log at key points: function entry, API calls, state changes, errors, and decision branches + +### Status Messages +- **ALWAYS** update `STATUS_MESSAGES.md` when adding or modifying user-facing status messages +- Use the `setStatus(message, color)` function for all UI status updates +- Use appropriate `STATUS_COLORS` constants: + - `STATUS_COLORS.idle` - Default/waiting state + - `STATUS_COLORS. success` - Successful operations + - `STATUS_COLORS.warning` - Warning conditions + - `STATUS_COLORS.error` - Error states + - `STATUS_COLORS.info` - Informational/in-progress states + +--- + +## Documentation Requirements + +### Code Comments +- Document complex logic with inline comments +- Use JSDoc-style comments for functions: + - `@param` for parameters + - `@returns` for return values + - Brief description of purpose + +### docs/STATUS_MESSAGES.md Updates +When adding new status messages, include: +- The exact status message text +- When it appears (trigger condition) +- The status color used +- Any follow-up actions or states + +### `docs/CONNECTION_WORKFLOW.md` Updates +When **modifying connect or disconnect logic**, you must: +- Read `docs/CONNECTION_WORKFLOW.md` before making the change (to understand current intended behavior). +- Update `docs/CONNECTION_WORKFLOW.md` so it remains accurate after the change: + - Steps/sequence of the workflow + - Any new states, retries, timeouts, or error handling + - Any UI impacts (buttons, indicators, status messages) + +--- +## Requested Change: Update App Connection Flow (Reorder Steps) + +### Background +Below is the **current** app connection flow used when a user connects to a device for wardriving. + +#### Current Connection Flow +1. **User Initiates** → User clicks **Connect** +2. **Device Selection** → Browser displays BLE device picker +3. **BLE GATT Connection** → App establishes a GATT connection to the selected device +4. **Protocol Handshake** → App and device exchange/confirm protocol version compatibility +5. **Device Info** → App retrieves device metadata (e.g., device name, public key, settings) +6. **Time Sync** → App synchronizes the device clock +7. **Channel Setup** → App creates or finds the `#wardriving` channel +8. **GPS Init** → App starts GPS tracking +9. **Capacity Check** → App acquires an API slot from **MeshMapper** +10. **Connected** → App enables all controls; system is ready for wardriving + +--- + +### Requested Change + From 6ea8ff5af00b133188d4561bc7d8d204bf7f2eeb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 04:02:27 +0000 Subject: [PATCH 26/47] Initial plan From 16d8e944b67287fe3b0bc23cec1e3f29339e54ff Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 04:06:47 +0000 Subject: [PATCH 27/47] Reorder connection flow: move capacity check before channel setup and GPS init Co-authored-by: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com> --- content/wardrive.js | 18 +++++----- docs/CONNECTION_WORKFLOW.md | 66 ++++++++++++++++++------------------- 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/content/wardrive.js b/content/wardrive.js index 9215568..6c2f46d 100644 --- a/content/wardrive.js +++ b/content/wardrive.js @@ -2060,10 +2060,7 @@ async function connect() { debugLog("Device time sync not available or failed"); } try { - await ensureChannel(); - await primeGpsOnce(); - - // Check capacity after GPS is initialized + // Check capacity immediately after time sync, before channel setup and GPS init const allowed = await checkCapacity("connect"); if (!allowed) { debugWarn("Capacity check denied, disconnecting"); @@ -2073,11 +2070,16 @@ async function connect() { setTimeout(() => { disconnect().catch(err => debugError(`Disconnect after capacity denial failed: ${err.message}`)); }, 1500); - } else { - // Connection complete, show Connected status - setStatus("Connected", STATUS_COLORS.success); - debugLog("Full connection process completed successfully"); + return; } + + // Capacity check passed, proceed with channel setup and GPS initialization + await ensureChannel(); + await primeGpsOnce(); + + // Connection complete, show Connected status + setStatus("Connected", STATUS_COLORS.success); + debugLog("Full connection process completed successfully"); } catch (e) { debugError(`Channel setup failed: ${e.message}`, e); setStatus(e.message || "Channel setup failed", STATUS_COLORS.error); diff --git a/docs/CONNECTION_WORKFLOW.md b/docs/CONNECTION_WORKFLOW.md index 6824cf8..114336e 100644 --- a/docs/CONNECTION_WORKFLOW.md +++ b/docs/CONNECTION_WORKFLOW.md @@ -17,8 +17,8 @@ **What "Connect" Means:** - Establishes a Web Bluetooth (BLE) connection to a MeshCore companion device - Configures the device for wardriving operations -- Creates or finds the `#wardriving` channel for sending GPS pings - Acquires an API slot from the MeshMapper backend for capacity management +- Creates or finds the `#wardriving` channel for sending GPS pings - Initializes GPS tracking for location services - Enables the app to send wardrive pings to the mesh network @@ -59,9 +59,9 @@ 4. **Protocol Handshake** → Exchanges protocol version 5. **Device Info** → Retrieves device name, public key, settings 6. **Time Sync** → Synchronizes device clock -7. **Channel Setup** → Creates/finds #wardriving channel -8. **GPS Init** → Starts GPS tracking -9. **Capacity Check** → Acquires API slot from MeshMapper +7. **Capacity Check** → Acquires API slot from MeshMapper +8. **Channel Setup** → Creates/finds #wardriving channel +9. **GPS Init** → Starts GPS tracking 10. **Connected** → Enables all controls, ready for wardriving ### Detailed Connection Steps @@ -116,7 +116,20 @@ connectBtn.addEventListener("click", async () => { - Device updates its clock - Optional, errors ignored -7. **Setup Channel** +7. **Check Capacity** + - POSTs to MeshMapper API: + ```json + { + "key": "API_KEY", + "public_key": "device_hex_key", + "who": "device_name", + "reason": "connect" + } + ``` + - If `allowed: false` → disconnects with error + - If API error → disconnects (fail-closed) + +8. **Setup Channel** - Searches for existing `#wardriving` channel - If not found, creates new channel: - Finds empty channel slot @@ -125,7 +138,7 @@ connectBtn.addEventListener("click", async () => { - Stores channel object in `state.channel` - Updates UI: "#wardriving (CH:X)" -8. **Initialize GPS** +9. **Initialize GPS** - Requests location permission - Gets initial GPS position (30s timeout) - Starts continuous GPS watch @@ -134,19 +147,6 @@ connectBtn.addEventListener("click", async () => { - Updates UI with coordinates and accuracy - Refreshes coverage map if accuracy < 100m -9. **Check Capacity** - - POSTs to MeshMapper API: - ```json - { - "key": "API_KEY", - "public_key": "device_hex_key", - "who": "device_name", - "reason": "connect" - } - ``` - - If `allowed: false` → disconnects with error - - If API error → disconnects (fail-closed) - 10. **Connection Complete** - Sets status to "Connected" (green) - Enables all UI controls @@ -271,18 +271,16 @@ sequenceDiagram App->>Device: syncDeviceTime() Device-->>App: OK - App->>Device: findChannel("#wardriving") - alt Not found - App->>Device: createChannel() - end - - App->>GPS: getCurrentPosition() - GPS-->>App: Position - App->>GPS: watchPosition() - App->>API: checkCapacity("connect") alt Allowed API-->>App: {allowed: true} + App->>Device: findChannel("#wardriving") + alt Not found + App->>Device: createChannel() + end + App->>GPS: getCurrentPosition() + GPS-->>App: Position + App->>GPS: watchPosition() App->>UI: Status "Connected" else Denied API-->>App: {allowed: false} @@ -343,16 +341,16 @@ stateDiagram-v2 BLEConnection --> DeviceInfo: GATT connected BLEConnection --> Disconnected: GATT fails - DeviceInfo --> ChannelSetup: Info valid + DeviceInfo --> CapacityCheck: Info valid DeviceInfo --> Disconnecting: Invalid key + CapacityCheck --> ChannelSetup: Slot acquired + CapacityCheck --> Disconnecting: Denied + ChannelSetup --> GPSInit: Channel ready ChannelSetup --> Disconnecting: Setup fails - GPSInit --> CapacityCheck: GPS started - - CapacityCheck --> Connected: Slot acquired - CapacityCheck --> Disconnecting: Denied + GPSInit --> Connected: GPS started Connected --> Disconnecting: User disconnect Connected --> Disconnecting: BLE lost @@ -489,7 +487,7 @@ MeshCore-GOME-WarDriver implements a robust Web Bluetooth wardriving application 4. **Clear State Machine**: No ambiguous states 5. **User Transparency**: Status messages at every step -**Connection:** BLE → Device Info → Channel Setup → GPS → Capacity Check → Connected +**Connection:** BLE → Device Info → Time Sync → Capacity Check → Channel Setup → GPS → Connected **Disconnection:** Capacity Release → Channel Delete → BLE Close → Full Cleanup → Disconnected From ab1a40b3ed8483bcedfa67d5ea674c2a6e2714b1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 04:07:54 +0000 Subject: [PATCH 28/47] Update STATUS_MESSAGES.md to reflect new connection flow order Co-authored-by: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com> --- docs/STATUS_MESSAGES.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/STATUS_MESSAGES.md b/docs/STATUS_MESSAGES.md index cbf7abd..e99fe77 100644 --- a/docs/STATUS_MESSAGES.md +++ b/docs/STATUS_MESSAGES.md @@ -25,16 +25,16 @@ Status messages follow these consistent conventions: - **Message**: `"Connecting"` - **Color**: Sky blue (info) - **Used in**: `connect()` -- **Source**: `content/wardrive.js:2005` -- **Context**: When user clicks Connect button; remains visible during entire connection process (BLE pairing, channel setup, and capacity check) +- **Source**: `content/wardrive.js:2021` +- **Context**: When user clicks Connect button; remains visible during entire connection process (BLE pairing, capacity check, and channel setup) - **Minimum Visibility**: Natural async timing during full connection process (typically 3-8 seconds including capacity check) #### Connected - **Message**: `"Connected"` - **Color**: Green (success) - **Used in**: `connect()` -- **Source**: `content/wardrive.js:2060` -- **Context**: After full connection process completes successfully (BLE paired, channel setup, and capacity check passed) +- **Source**: `content/wardrive.js:2080` +- **Context**: After full connection process completes successfully (BLE paired, capacity check passed, channel setup, and GPS initialized) - **Minimum Visibility**: Persists until user interacts with app buttons (send ping, start auto mode) - **Note**: This message now only appears after the complete connection handshake, not just after BLE pairing @@ -87,8 +87,8 @@ Status messages follow these consistent conventions: - **Message**: `"Acquiring wardriving slot"` - **Color**: Sky blue (info) - **Used in**: `checkCapacity()` -- **Source**: `content/wardrive.js:1026` -- **Context**: When connecting to device and checking if a wardriving slot is available +- **Source**: `content/wardrive.js:1027` +- **Context**: When connecting to device, after time sync and before channel setup, checking if a wardriving slot is available - **Minimum Visibility**: 500ms minimum enforced (or until API response received) #### Disconnected: WarDriving app has reached capacity From 7665c294782220be1c1cdbbbace4e6a527ed511f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 04:15:25 +0000 Subject: [PATCH 29/47] Initial plan From 8f95b87fe6bf67952b1c538c78801a6d76686698 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 04:20:17 +0000 Subject: [PATCH 30/47] Add build-time app version to API payload with DEV/Release support Co-authored-by: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com> --- .github/workflows/deploy.yml | 6 ++-- content/wardrive.js | 54 +++++++++++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index de98397..110db3a 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -48,16 +48,18 @@ jobs: mkdir -p _site cp -r main-content/* _site/ 2>/dev/null || true - # Inject version into main branch + # Inject version into main branch (both visual display and data attribute) sed -i 's|MeshCore Wardrive|MeshCore Wardrive '"${RELEASE_VERSION}"'|' _site/index.html + sed -i 's|||' _site/index.html mkdir -p _site/dev cp -r dev-content/* _site/dev/ 2>/dev/null || true cp -r dev-content/content _site/dev/ 2>/dev/null || true - # Inject dev badge with date + # Inject dev badge with date (both visual display and data attribute) DEV_DATE=$(date -u +"%Y-%m-%d %H:%M UTC") sed -i 's|MeshCore Wardrive|MeshCore Wardrive DEV '"${DEV_DATE}"'|' _site/dev/index.html + sed -i 's|||' _site/dev/index.html find _site -name ". git" -exec rm -rf {} + 2>/dev/null || true find _site -name ".github" -exec rm -rf {} + 2>/dev/null || true diff --git a/content/wardrive.js b/content/wardrive.js index 6c2f46d..9c70559 100644 --- a/content/wardrive.js +++ b/content/wardrive.js @@ -79,6 +79,57 @@ const MESHMAPPER_CAPACITY_CHECK_URL = "https://yow.meshmapper.net/capacitycheck. const MESHMAPPER_API_KEY = "59C7754DABDF5C11CA5F5D8368F89"; const MESHMAPPER_DEFAULT_WHO = "GOME-WarDriver"; // Default identifier +// ---- App Version Configuration ---- +/** + * Extract and format the app version from the HTML data attribute. + * GitHub Actions injects the version into the body's data-app-version attribute. + * @returns {string} Formatted version string (e.g., "1.3.0" or "DEV-1734652800") + */ +function getAppVersion() { + try { + const versionAttr = document.body.getAttribute('data-app-version'); + + if (!versionAttr) { + debugWarn('App version not found in HTML, using fallback'); + return 'UNKNOWN'; + } + + debugLog(`Raw version from HTML: ${versionAttr}`); + + // Check if version is a date string (contains spaces or hyphens followed by time) + // DEV versions look like: "2024-12-20 04:16 UTC" or similar + // Release versions look like: "v1.3.0" or "1.3.0" + const isDateString = /\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}/.test(versionAttr); + + if (isDateString) { + // Parse the date and convert to EPOCH seconds + const parsedDate = new Date(versionAttr); + + if (isNaN(parsedDate.getTime())) { + debugWarn(`Failed to parse DEV date: ${versionAttr}`); + return 'DEV-UNKNOWN'; + } + + // Convert to EPOCH seconds (not milliseconds) + const epochSeconds = Math.floor(parsedDate.getTime() / 1000); + const devVersion = `DEV-${epochSeconds}`; + debugLog(`DEV version computed: ${devVersion} (from ${versionAttr})`); + return devVersion; + } else { + // Release version - use as-is + debugLog(`Release version: ${versionAttr}`); + return versionAttr; + } + } catch (error) { + debugError(`Error extracting app version: ${error.message}`); + return 'ERROR'; + } +} + +// Cache the app version at startup +const APP_VERSION = getAppVersion(); +debugLog(`App version initialized: ${APP_VERSION}`); + // ---- DOM refs (from index.html; unchanged except the two new selectors) ---- const $ = (id) => document.getElementById(id); const statusEl = $("status"); @@ -1096,10 +1147,11 @@ async function postToMeshMapperAPI(lat, lon, heardRepeats) { who: getDeviceIdentifier(), power: getCurrentPowerSetting() || "N/A", heard_repeats: heardRepeats, + ver: APP_VERSION, test: 0 }; - debugLog(`Posting to MeshMapper API: lat=${lat.toFixed(5)}, lon=${lon.toFixed(5)}, who=${payload.who}, power=${payload.power}, heard_repeats=${heardRepeats}`); + debugLog(`Posting to MeshMapper API: lat=${lat.toFixed(5)}, lon=${lon.toFixed(5)}, who=${payload.who}, power=${payload.power}, heard_repeats=${heardRepeats}, ver=${payload.ver}`); const response = await fetch(MESHMAPPER_API_URL, { method: "POST", From 5d6fb69fb3cd747b372d76b2115d6dc9969a9dbb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 04:22:41 +0000 Subject: [PATCH 31/47] Refactor: Inject version directly into JS constant instead of HTML attribute Co-authored-by: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com> --- .github/workflows/deploy.yml | 9 +++--- content/wardrive.js | 53 +++--------------------------------- 2 files changed, 9 insertions(+), 53 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 110db3a..5a6a54b 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -48,18 +48,19 @@ jobs: mkdir -p _site cp -r main-content/* _site/ 2>/dev/null || true - # Inject version into main branch (both visual display and data attribute) + # Inject version into main branch (visual display and JS constant) sed -i 's|MeshCore Wardrive|MeshCore Wardrive '"${RELEASE_VERSION}"'|' _site/index.html - sed -i 's|||' _site/index.html + sed -i 's|const APP_VERSION = "UNKNOWN";|const APP_VERSION = "'"${RELEASE_VERSION}"'";|' _site/content/wardrive.js mkdir -p _site/dev cp -r dev-content/* _site/dev/ 2>/dev/null || true cp -r dev-content/content _site/dev/ 2>/dev/null || true - # Inject dev badge with date (both visual display and data attribute) + # Inject dev badge with date (visual display and JS constant with DEV-EPOCH format) DEV_DATE=$(date -u +"%Y-%m-%d %H:%M UTC") + DEV_EPOCH=$(date -u +%s) sed -i 's|MeshCore Wardrive|MeshCore Wardrive DEV '"${DEV_DATE}"'|' _site/dev/index.html - sed -i 's|||' _site/dev/index.html + sed -i 's|const APP_VERSION = "UNKNOWN";|const APP_VERSION = "DEV-'"${DEV_EPOCH}"'";|' _site/dev/content/wardrive.js find _site -name ". git" -exec rm -rf {} + 2>/dev/null || true find _site -name ".github" -exec rm -rf {} + 2>/dev/null || true diff --git a/content/wardrive.js b/content/wardrive.js index 9c70559..7d1cadb 100644 --- a/content/wardrive.js +++ b/content/wardrive.js @@ -80,55 +80,10 @@ const MESHMAPPER_API_KEY = "59C7754DABDF5C11CA5F5D8368F89"; const MESHMAPPER_DEFAULT_WHO = "GOME-WarDriver"; // Default identifier // ---- App Version Configuration ---- -/** - * Extract and format the app version from the HTML data attribute. - * GitHub Actions injects the version into the body's data-app-version attribute. - * @returns {string} Formatted version string (e.g., "1.3.0" or "DEV-1734652800") - */ -function getAppVersion() { - try { - const versionAttr = document.body.getAttribute('data-app-version'); - - if (!versionAttr) { - debugWarn('App version not found in HTML, using fallback'); - return 'UNKNOWN'; - } - - debugLog(`Raw version from HTML: ${versionAttr}`); - - // Check if version is a date string (contains spaces or hyphens followed by time) - // DEV versions look like: "2024-12-20 04:16 UTC" or similar - // Release versions look like: "v1.3.0" or "1.3.0" - const isDateString = /\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}/.test(versionAttr); - - if (isDateString) { - // Parse the date and convert to EPOCH seconds - const parsedDate = new Date(versionAttr); - - if (isNaN(parsedDate.getTime())) { - debugWarn(`Failed to parse DEV date: ${versionAttr}`); - return 'DEV-UNKNOWN'; - } - - // Convert to EPOCH seconds (not milliseconds) - const epochSeconds = Math.floor(parsedDate.getTime() / 1000); - const devVersion = `DEV-${epochSeconds}`; - debugLog(`DEV version computed: ${devVersion} (from ${versionAttr})`); - return devVersion; - } else { - // Release version - use as-is - debugLog(`Release version: ${versionAttr}`); - return versionAttr; - } - } catch (error) { - debugError(`Error extracting app version: ${error.message}`); - return 'ERROR'; - } -} - -// Cache the app version at startup -const APP_VERSION = getAppVersion(); -debugLog(`App version initialized: ${APP_VERSION}`); +// This constant is injected by GitHub Actions during build/deploy +// For release builds: Contains the release version (e.g., "v1.3.0") +// For DEV builds: Contains "DEV-" format (e.g., "DEV-1734652800") +const APP_VERSION = "UNKNOWN"; // Placeholder - replaced during build // ---- DOM refs (from index.html; unchanged except the two new selectors) ---- const $ = (id) => document.getElementById(id); From 31eed4d6c45ebc6bdd68d1129fc37b861bb61f32 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 04:28:40 +0000 Subject: [PATCH 32/47] Initial plan From b79435aa3e094dd63a5f90c18912c474fbdf41fb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 04:35:03 +0000 Subject: [PATCH 33/47] Add status messages for connection flow and slot revocation Co-authored-by: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com> --- content/wardrive.js | 24 ++++++-- docs/CONNECTION_WORKFLOW.md | 79 +++++++++++++++++++++--- docs/STATUS_MESSAGES.md | 118 ++++++++++++++++++++++++++++-------- 3 files changed, 185 insertions(+), 36 deletions(-) diff --git a/content/wardrive.js b/content/wardrive.js index 7d1cadb..dddca09 100644 --- a/content/wardrive.js +++ b/content/wardrive.js @@ -951,13 +951,16 @@ async function ensureChannel() { return state.channel; } + setStatus("Looking for #wardriving channel", STATUS_COLORS.info); debugLog(`Looking up channel: ${CHANNEL_NAME}`); let ch = await state.connection.findChannelByName(CHANNEL_NAME); if (!ch) { + setStatus("Channel #wardriving not found", STATUS_COLORS.info); debugLog(`Channel ${CHANNEL_NAME} not found, attempting to create it`); try { ch = await createWardriveChannel(); + setStatus("Created #wardriving", STATUS_COLORS.success); debugLog(`Channel ${CHANNEL_NAME} created successfully`); } catch (e) { debugError(`Failed to create channel ${CHANNEL_NAME}: ${e.message}`); @@ -967,6 +970,7 @@ async function ensureChannel() { ); } } else { + setStatus("Channel #wardriving found", STATUS_COLORS.success); debugLog(`Channel found: ${CHANNEL_NAME} (index: ${ch.channelIdx})`); } @@ -1125,9 +1129,9 @@ async function postToMeshMapperAPI(lat, lon, heardRepeats) { // Check if slot has been revoked if (data.allowed === false) { debugWarn("MeshMapper API returned allowed=false, WarDriving slot has been revoked, disconnecting"); - setStatus("Disconnected: WarDriving slot has been revoked", STATUS_COLORS.error); + setStatus("Error: Posting to API (Revoked)", STATUS_COLORS.error); state.disconnectReason = "slot_revoked"; // Track disconnect reason - // Disconnect after a brief delay to ensure user sees the message + // Disconnect after a brief delay to ensure user sees the error message setTimeout(() => { disconnect().catch(err => debugError(`Disconnect after slot revocation failed: ${err.message}`)); }, 1500); @@ -2080,8 +2084,16 @@ async function connect() { return; } - // Capacity check passed, proceed with channel setup and GPS initialization + // Capacity check passed + setStatus("Acquired wardriving slot", STATUS_COLORS.success); + debugLog("Wardriving slot acquired successfully"); + + // Proceed with channel setup and GPS initialization await ensureChannel(); + + // GPS initialization + setStatus("Priming GPS", STATUS_COLORS.info); + debugLog("Starting GPS initialization"); await primeGpsOnce(); // Connection complete, show Connected status @@ -2098,9 +2110,13 @@ async function connect() { debugLog("BLE disconnected event fired"); // Only set "Disconnected" status for normal disconnections - // Preserve error messages (app_down, capacity_full, error) instead of overwriting + // Preserve error messages (app_down, capacity_full, error, slot_revoked) instead of overwriting if (state.disconnectReason === "normal" || state.disconnectReason === null || state.disconnectReason === undefined) { setStatus("Disconnected", STATUS_COLORS.error); + } else if (state.disconnectReason === "slot_revoked") { + // For slot revocation, set the terminal status message + setStatus("Disconnected: WarDriving slot has been revoked", STATUS_COLORS.error); + debugLog("Setting terminal status for slot revocation"); } else { debugLog(`Preserving disconnect status for reason: ${state.disconnectReason}`); } diff --git a/docs/CONNECTION_WORKFLOW.md b/docs/CONNECTION_WORKFLOW.md index 114336e..288a144 100644 --- a/docs/CONNECTION_WORKFLOW.md +++ b/docs/CONNECTION_WORKFLOW.md @@ -66,7 +66,7 @@ ### Detailed Connection Steps -See `content/wardrive.js` lines 2002-2118 for the main `connect()` function. +See `content/wardrive.js` lines 2020-2150 for the main `connect()` function. **Key Entry Point:** ```javascript @@ -85,12 +85,14 @@ connectBtn.addEventListener("click", async () => { - Checks `navigator.bluetooth` exists - Alerts user if not supported - Fails fast if unavailable + - **Status**: N/A (alert shown) 2. **Open BLE Connection** - Calls `WebBleConnection.open()` (web_ble_connection.js:15-41) - Shows browser's native device picker - Filters for MeshCore BLE service UUID - User selects device or cancels + - **Status**: `"Connecting"` (blue) 3. **Initialize BLE** - Connects to GATT server @@ -98,10 +100,12 @@ connectBtn.addEventListener("click", async () => { - Starts notifications on TX characteristic - Sets up frame listener for incoming data - Fires "connected" event + - **Status**: `"Connecting"` (blue, maintained) 4. **Device Query** - Sends protocol version query - Non-critical, errors ignored + - **Status**: `"Connecting"` (blue, maintained) 5. **Get Device Info** - Retrieves device name, public key (32 bytes), settings @@ -110,13 +114,16 @@ connectBtn.addEventListener("click", async () => { - Stores in `state.devicePublicKey` - Updates UI with device name - Changes button to "Disconnect" (red) + - **Status**: `"Connecting"` (blue, maintained) 6. **Sync Device Time** - Sends current Unix timestamp - Device updates its clock - Optional, errors ignored + - **Status**: `"Connecting"` (blue, maintained) 7. **Check Capacity** + - **Status**: `"Acquiring wardriving slot"` (blue) - POSTs to MeshMapper API: ```json { @@ -128,17 +135,27 @@ connectBtn.addEventListener("click", async () => { ``` - If `allowed: false` → disconnects with error - If API error → disconnects (fail-closed) + - On success → **Status**: `"Acquired wardriving slot"` (green) 8. **Setup Channel** + - **Status**: `"Looking for #wardriving channel"` (blue) - Searches for existing `#wardriving` channel - - If not found, creates new channel: - - Finds empty channel slot - - Derives channel key: `SHA-256(#wardriving).slice(0, 16)` - - Sends setChannel command - - Stores channel object in `state.channel` - - Updates UI: "#wardriving (CH:X)" + - If found: + - **Status**: `"Channel #wardriving found"` (green) + - Stores channel object in `state.channel` + - Updates UI: "#wardriving (CH:X)" + - If not found: + - **Status**: `"Channel #wardriving not found"` (blue) + - Creates new channel: + - Finds empty channel slot + - Derives channel key: `SHA-256(#wardriving).slice(0, 16)` + - Sends setChannel command + - **Status**: `"Created #wardriving"` (green) + - Stores channel object in `state.channel` + - Updates UI: "#wardriving (CH:X)" 9. **Initialize GPS** + - **Status**: `"Priming GPS"` (blue) - Requests location permission - Gets initial GPS position (30s timeout) - Starts continuous GPS watch @@ -148,7 +165,7 @@ connectBtn.addEventListener("click", async () => { - Refreshes coverage map if accuracy < 100m 10. **Connection Complete** - - Sets status to "Connected" (green) + - **Status**: `"Connected"` (green) - Enables all UI controls - Ready for wardriving operations @@ -186,6 +203,7 @@ See `content/wardrive.js` lines 2119-2179 for the main `disconnect()` function. - "capacity_full" - MeshMapper full - "app_down" - API unavailable - "error" - validation/setup failure + - "slot_revoked" - slot revoked during active session 3. **Update Status** - Sets status to "Disconnecting" (blue) @@ -237,6 +255,51 @@ See `content/wardrive.js` lines 2119-2179 for the main `disconnect()` function. - All resources released - Ready for new connection +### Slot Revocation Workflow + +When a wardriving slot is revoked during an active session (detected during API posting), a special disconnect sequence occurs: + +**Revocation Detection:** +- Occurs during `postToMeshMapperAPI()` call (after every ping) +- API response contains `allowed: false` +- This indicates the backend has revoked the device's slot + +**Revocation Sequence:** + +1. **Detection** + - During "Posting to API" operation + - API returns `{"allowed": false, ...}` + - Detected in `postToMeshMapperAPI()` response handler + +2. **Initial Status** + - **Status**: `"Error: Posting to API (Revoked)"` (red) + - Sets `state.disconnectReason = "slot_revoked"` + - Visible for 1.5 seconds + +3. **Disconnect Initiated** + - Calls `disconnect()` after 1.5s delay + - **Status**: `"Disconnecting"` (blue) + - Proceeds with normal disconnect cleanup + +4. **Terminal Status** + - Disconnect event handler detects `slot_revoked` reason + - **Status**: `"Disconnected: WarDriving slot has been revoked"` (red) + - This is the final terminal status (does NOT revert to "Idle") + +**Complete Revocation Flow:** +``` +"Posting to API" (blue) + → "Error: Posting to API (Revoked)" (red, 1.5s) + → "Disconnecting" (blue) + → "Disconnected: WarDriving slot has been revoked" (red, terminal) +``` + +**Key Differences from Normal Disconnect:** +- Normal disconnect: ends with "Disconnected" (red) +- Revocation: ends with "Disconnected: WarDriving slot has been revoked" (red) +- Revocation shows intermediate "Error: Posting to API (Revoked)" state +- Terminal status is preserved and does not loop to "Idle" + ## Workflow Diagrams ### Connection Sequence diff --git a/docs/STATUS_MESSAGES.md b/docs/STATUS_MESSAGES.md index e99fe77..7f3923c 100644 --- a/docs/STATUS_MESSAGES.md +++ b/docs/STATUS_MESSAGES.md @@ -87,33 +87,51 @@ Status messages follow these consistent conventions: - **Message**: `"Acquiring wardriving slot"` - **Color**: Sky blue (info) - **Used in**: `checkCapacity()` -- **Source**: `content/wardrive.js:1027` +- **Source**: `content/wardrive.js:1033` - **Context**: When connecting to device, after time sync and before channel setup, checking if a wardriving slot is available - **Minimum Visibility**: 500ms minimum enforced (or until API response received) +#### Acquired wardriving slot +- **Message**: `"Acquired wardriving slot"` +- **Color**: Green (success) +- **Used in**: `connect()` +- **Source**: `content/wardrive.js:2087` +- **Context**: Capacity check passed successfully, slot acquired from MeshMapper API +- **Minimum Visibility**: 500ms minimum enforced +- **Notes**: This message appears after "Acquiring wardriving slot" when the API confirms slot availability. Fixes spelling from previous "Aquired" typo. + #### Disconnected: WarDriving app has reached capacity - **Message**: `"Disconnected: WarDriving app has reached capacity"` - **Color**: Red (error) - **Used in**: `checkCapacity()` -- **Source**: `content/wardrive.js:1063` +- **Source**: `content/wardrive.js:1069` - **Context**: Capacity check API denies slot on connect (returns allowed=false) - **Minimum Visibility**: N/A (error state persists; message is preserved during automatic disconnect) - **Notes**: Displayed when the API successfully responds but indicates capacity is full at connection time. When this error occurs during connection, the automatic disconnect flow preserves this status message instead of showing "Disconnected". Message format standardized with "Disconnected: " prefix to clearly indicate disconnect state. +#### Error: Posting to API (Revoked) +- **Message**: `"Error: Posting to API (Revoked)"` +- **Color**: Red (error) +- **Used in**: `postToMeshMapperAPI()` +- **Source**: `content/wardrive.js:1128` +- **Context**: Intermediate status shown when WarDriving API returns allowed=false during an active session +- **Minimum Visibility**: 1500ms (enforced by setTimeout delay before disconnect) +- **Notes**: This is the first status message shown when slot revocation is detected during API posting. After the delay, the disconnect sequence begins with "Disconnecting", followed by the terminal status "Disconnected: WarDriving slot has been revoked". + #### Disconnected: WarDriving slot has been revoked - **Message**: `"Disconnected: WarDriving slot has been revoked"` - **Color**: Red (error) -- **Used in**: `postToMeshMapperAPI()` -- **Source**: `content/wardrive.js:1121` -- **Context**: WarDriving API returns allowed=false during an active session (every POST to wardriving-api.php) -- **Minimum Visibility**: N/A (error state persists; message is preserved during automatic disconnect) -- **Notes**: Displayed when the API responds with `allowed=false`, indicating the device's WarDriving slot has been revoked. This check happens on every POST to the API endpoint, regardless of HTTP status code. Debug messages are logged when `?debug=true` is in the URL. When this error occurs, the device is immediately disconnected and the automatic disconnect flow preserves this status message instead of showing "Disconnected". Message format standardized with "Disconnected: " prefix to clearly indicate disconnect state. +- **Used in**: `connect()` (disconnected event handler) +- **Source**: `content/wardrive.js:2123` +- **Context**: Terminal status shown when WarDriving slot is revoked during an active session +- **Minimum Visibility**: N/A (error state persists as terminal status) +- **Notes**: This is the final status message in the slot revocation flow. The complete sequence is: "Posting to API" → "Error: Posting to API (Revoked)" → "Disconnecting" → "Disconnected: WarDriving slot has been revoked". This message is set by the disconnect event handler when state.disconnectReason is "slot_revoked". Message format standardized with "Disconnected: " prefix to clearly indicate disconnect state. #### Disconnected: WarDriving app is down - **Message**: `"Disconnected: WarDriving app is down"` - **Color**: Red (error) - **Used in**: `checkCapacity()` -- **Source**: `content/wardrive.js:1050`, `content/wardrive.js:1074` +- **Source**: `content/wardrive.js:1057`, `content/wardrive.js:1081` - **Context**: Capacity check API returns error status or network is unreachable during connect - **Minimum Visibility**: N/A (error state persists; message is preserved during automatic disconnect) - **Notes**: Implements fail-closed policy - connection is denied if API fails or is unreachable. When this error occurs, the automatic disconnect flow preserves this status message instead of showing "Disconnected". Message format standardized with "Disconnected: " prefix to clearly indicate disconnect state. @@ -122,7 +140,7 @@ Status messages follow these consistent conventions: - **Message**: `"Unable to read device public key; try again"` - **Color**: Red (error) - **Used in**: `connect()` -- **Source**: `content/wardrive.js:2023` +- **Source**: `content/wardrive.js:2048` - **Context**: Device public key is missing or invalid when trying to acquire capacity slot - **Minimum Visibility**: N/A (error state persists until disconnect) @@ -136,7 +154,56 @@ Status messages follow these consistent conventions: --- -### 3. Ping Operation Messages +### 3. Channel Setup Messages + +#### Looking for #wardriving channel +- **Message**: `"Looking for #wardriving channel"` +- **Color**: Sky blue (info) +- **Used in**: `ensureChannel()` +- **Source**: `content/wardrive.js:954` +- **Context**: During connection setup, after capacity check, searching for existing #wardriving channel +- **Minimum Visibility**: 500ms minimum enforced + +#### Channel #wardriving found +- **Message**: `"Channel #wardriving found"` +- **Color**: Green (success) +- **Used in**: `ensureChannel()` +- **Source**: `content/wardrive.js:971` +- **Context**: Existing #wardriving channel found on device +- **Minimum Visibility**: 500ms minimum enforced + +#### Channel #wardriving not found +- **Message**: `"Channel #wardriving not found"` +- **Color**: Sky blue (info) +- **Used in**: `ensureChannel()` +- **Source**: `content/wardrive.js:958` +- **Context**: #wardriving channel does not exist, will attempt to create it +- **Minimum Visibility**: 500ms minimum enforced + +#### Created #wardriving +- **Message**: `"Created #wardriving"` +- **Color**: Green (success) +- **Used in**: `ensureChannel()` +- **Source**: `content/wardrive.js:962` +- **Context**: Successfully created new #wardriving channel on device +- **Minimum Visibility**: 500ms minimum enforced + +--- + +### 4. GPS Initialization Messages + +#### Priming GPS +- **Message**: `"Priming GPS"` +- **Color**: Sky blue (info) +- **Used in**: `connect()` +- **Source**: `content/wardrive.js:2101` +- **Context**: Starting GPS initialization during connection setup +- **Minimum Visibility**: 500ms minimum enforced (or until GPS initialization completes) +- **Notes**: This status is shown after channel setup and before the final "Connected" status. + +--- + +### 5. Ping Operation Messages #### Sending manual ping - **Message**: `"Sending manual ping"` @@ -197,13 +264,13 @@ Status messages follow these consistent conventions: --- -### 4. GPS Status Messages +### 6. GPS Status Messages #### Waiting for GPS fix - **Message**: `"Waiting for GPS fix"` - **Color**: Amber (warning) - **Used in**: `getGpsCoordinatesForPing()` -- **Source**: `content/wardrive.js:1503` +- **Source**: `content/wardrive.js:1614` - **Context**: Auto ping triggered but no GPS lock acquired yet - **Minimum Visibility**: 500ms minimum enforced @@ -211,13 +278,13 @@ Status messages follow these consistent conventions: - **Message**: `"GPS data too old, requesting fresh position"` - **Color**: Amber (warning) - **Used in**: `getGpsCoordinatesForPing()` -- **Source**: `content/wardrive.js:1514`, `content/wardrive.js:1567` +- **Source**: `content/wardrive.js:1625`, `content/wardrive.js:1678` - **Context**: GPS data is stale and needs refresh (used in both auto and manual ping modes) - **Minimum Visibility**: 500ms minimum enforced --- -### 5. Countdown Timer Messages +### 7. Countdown Timer Messages These messages use a hybrid approach: **first display respects 500ms minimum**, then updates occur immediately every second. @@ -273,13 +340,13 @@ These messages use a hybrid approach: **first display respects 500ms minimum**, --- -### 6. API and Map Update Messages +### 8. API and Map Update Messages #### Posting to API - **Message**: `"Posting to API"` - **Color**: Sky blue (info) - **Used in**: `postApiAndRefreshMap()` -- **Source**: `content/wardrive.js:1055` +- **Source**: `content/wardrive.js:1167` - **Context**: After RX listening window, posting ping data to MeshMapper API - **Timing**: Visible during API POST operation (3-second hidden delay + API call time, typically ~3.5-4.5s total) - **Minimum Visibility**: 500ms minimum enforced (naturally ~4s due to 3s delay + API timing) @@ -289,20 +356,20 @@ These messages use a hybrid approach: **first display respects 500ms minimum**, - **Message**: `"Idle"` - **Color**: Slate (idle) - **Used in**: `postApiAndRefreshMap()` -- **Source**: `content/wardrive.js:1091` +- **Source**: `content/wardrive.js:1203` - **Context**: Manual mode after API post completes - **Minimum Visibility**: 500ms minimum enforced - **Note**: No longer shown after initial connection; "Connected" status is displayed instead and persists until user action --- -### 7. Auto Mode Messages +### 9. Auto Mode Messages #### Auto mode stopped - **Message**: `"Auto mode stopped"` - **Color**: Slate (idle) - **Used in**: `disconnect()` (event handler for stopping auto mode) -- **Source**: `content/wardrive.js:2070` +- **Source**: `content/wardrive.js:2247` - **Context**: User clicks "Stop Auto Ping" button - **Minimum Visibility**: 500ms minimum enforced @@ -310,7 +377,7 @@ These messages use a hybrid approach: **first display respects 500ms minimum**, - **Message**: `"Lost focus, auto mode stopped"` - **Color**: Amber (warning) - **Used in**: `disconnect()` (page visibility handler) -- **Source**: `content/wardrive.js:2032` +- **Source**: `content/wardrive.js:2209` - **Context**: Browser tab hidden while auto mode running - **Minimum Visibility**: 500ms minimum enforced @@ -318,7 +385,7 @@ These messages use a hybrid approach: **first display respects 500ms minimum**, - **Message**: `"Wait Xs before toggling auto mode"` (X is dynamic countdown) - **Color**: Amber (warning) - **Used in**: `stopAutoPing()`, `startAutoPing()` -- **Source**: `content/wardrive.js:1816`, `content/wardrive.js:1876` +- **Source**: `content/wardrive.js:1928`, `content/wardrive.js:1988` - **Context**: User attempts to toggle auto mode during cooldown period - **Minimum Visibility**: 500ms minimum enforced @@ -381,11 +448,13 @@ Result: "Message A" (visible 500ms) → "Message C" ## Summary -**Total Status Messages**: 31 unique message patterns +**Total Status Messages**: 38 unique message patterns - **Connection**: 7 messages -- **Capacity Check**: 5 messages (1 deprecated, 1 new for slot revocation) +- **Capacity Check**: 7 messages (1 deprecated, includes new "Acquired wardriving slot" and "Error: Posting to API (Revoked)") +- **Channel Setup**: 4 messages (new section for channel lookup and creation) +- **GPS Initialization**: 1 message (new "Priming GPS") - **Ping Operation**: 6 messages (consolidated "Ping sent" for both manual and auto) -- **GPS**: 2 messages +- **GPS Status**: 2 messages - **Countdown Timers**: 6 message patterns (with dynamic countdown values) - **API/Map**: 2 messages - **Auto Mode**: 3 messages @@ -400,3 +469,4 @@ Result: "Message A" (visible 500ms) → "Message C" - Consistent "X failed" format for error messages - Consistent tone (direct, technical) - removed "Please" from wait messages - Proper compound words ("geofenced" not "geo fenced") +- Correct spelling: "Acquired" (not "Aquired") From 624f329fd50930db7008451afc1ea26ebdf03704 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 04:55:28 +0000 Subject: [PATCH 34/47] Initial plan From 56f0d43344629da6cd4d92d8cfc3f4b133ddff84 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 04:58:54 +0000 Subject: [PATCH 35/47] Reorder status messages: show Disconnecting before terminal error messages Co-authored-by: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com> --- content/wardrive.js | 18 +++++++++++------- docs/CONNECTION_WORKFLOW.md | 10 ++++++++-- docs/STATUS_MESSAGES.md | 16 ++++++++-------- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/content/wardrive.js b/content/wardrive.js index dddca09..b2f7014 100644 --- a/content/wardrive.js +++ b/content/wardrive.js @@ -1058,7 +1058,6 @@ async function checkCapacity(reason) { // Fail closed on network errors for connect if (reason === "connect") { debugError("Failing closed (denying connection) due to API error"); - setStatus("Disconnected: WarDriving app is down", STATUS_COLORS.error); state.disconnectReason = "app_down"; // Track disconnect reason return false; } @@ -1070,7 +1069,6 @@ async function checkCapacity(reason) { // Handle capacity full vs. allowed cases separately if (data.allowed === false && reason === "connect") { - setStatus("Disconnected: WarDriving app has reached capacity", STATUS_COLORS.error); state.disconnectReason = "capacity_full"; // Track disconnect reason } @@ -1082,7 +1080,6 @@ async function checkCapacity(reason) { // Fail closed on network errors for connect if (reason === "connect") { debugError("Failing closed (denying connection) due to network error"); - setStatus("Disconnected: WarDriving app is down", STATUS_COLORS.error); state.disconnectReason = "app_down"; // Track disconnect reason return false; } @@ -2109,16 +2106,23 @@ async function connect() { conn.on("disconnected", () => { debugLog("BLE disconnected event fired"); - // Only set "Disconnected" status for normal disconnections - // Preserve error messages (app_down, capacity_full, error, slot_revoked) instead of overwriting - if (state.disconnectReason === "normal" || state.disconnectReason === null || state.disconnectReason === undefined) { - setStatus("Disconnected", STATUS_COLORS.error); + // Set appropriate status message based on disconnect reason + if (state.disconnectReason === "capacity_full") { + setStatus("Disconnected: WarDriving app has reached capacity", STATUS_COLORS.error); + debugLog("Setting terminal status for capacity full"); + } else if (state.disconnectReason === "app_down") { + setStatus("Disconnected: WarDriving app is down", STATUS_COLORS.error); + debugLog("Setting terminal status for app down"); } else if (state.disconnectReason === "slot_revoked") { // For slot revocation, set the terminal status message setStatus("Disconnected: WarDriving slot has been revoked", STATUS_COLORS.error); debugLog("Setting terminal status for slot revocation"); + } else if (state.disconnectReason === "normal" || state.disconnectReason === null || state.disconnectReason === undefined) { + setStatus("Disconnected", STATUS_COLORS.error); } else { + // For "error" or any other disconnect reason, preserve the current status or show generic disconnected debugLog(`Preserving disconnect status for reason: ${state.disconnectReason}`); + setStatus("Disconnected", STATUS_COLORS.error); } setConnectButton(false); diff --git a/docs/CONNECTION_WORKFLOW.md b/docs/CONNECTION_WORKFLOW.md index 288a144..14d2b5d 100644 --- a/docs/CONNECTION_WORKFLOW.md +++ b/docs/CONNECTION_WORKFLOW.md @@ -133,8 +133,14 @@ connectBtn.addEventListener("click", async () => { "reason": "connect" } ``` - - If `allowed: false` → disconnects with error - - If API error → disconnects (fail-closed) + - If `allowed: false`: + - Sets `state.disconnectReason = "capacity_full"` + - Triggers disconnect sequence after 1.5s delay + - **Status**: `"Disconnecting"` (blue) → `"Disconnected: WarDriving app has reached capacity"` (red) + - If API error: + - Sets `state.disconnectReason = "app_down"` + - Triggers disconnect sequence after 1.5s delay (fail-closed) + - **Status**: `"Disconnecting"` (blue) → `"Disconnected: WarDriving app is down"` (red) - On success → **Status**: `"Acquired wardriving slot"` (green) 8. **Setup Channel** diff --git a/docs/STATUS_MESSAGES.md b/docs/STATUS_MESSAGES.md index 7f3923c..3fe6075 100644 --- a/docs/STATUS_MESSAGES.md +++ b/docs/STATUS_MESSAGES.md @@ -103,11 +103,11 @@ Status messages follow these consistent conventions: #### Disconnected: WarDriving app has reached capacity - **Message**: `"Disconnected: WarDriving app has reached capacity"` - **Color**: Red (error) -- **Used in**: `checkCapacity()` -- **Source**: `content/wardrive.js:1069` +- **Used in**: `connect()` (disconnected event handler) +- **Source**: `content/wardrive.js` (disconnected event handler) - **Context**: Capacity check API denies slot on connect (returns allowed=false) -- **Minimum Visibility**: N/A (error state persists; message is preserved during automatic disconnect) -- **Notes**: Displayed when the API successfully responds but indicates capacity is full at connection time. When this error occurs during connection, the automatic disconnect flow preserves this status message instead of showing "Disconnected". Message format standardized with "Disconnected: " prefix to clearly indicate disconnect state. +- **Minimum Visibility**: N/A (error state persists as terminal status) +- **Notes**: This is the final status message when capacity check fails during connection. The complete sequence is: "Connecting" → "Acquiring wardriving slot" → "Disconnecting" → "Disconnected: WarDriving app has reached capacity". Message format standardized with "Disconnected: " prefix to clearly indicate disconnect state. #### Error: Posting to API (Revoked) - **Message**: `"Error: Posting to API (Revoked)"` @@ -130,11 +130,11 @@ Status messages follow these consistent conventions: #### Disconnected: WarDriving app is down - **Message**: `"Disconnected: WarDriving app is down"` - **Color**: Red (error) -- **Used in**: `checkCapacity()` -- **Source**: `content/wardrive.js:1057`, `content/wardrive.js:1081` +- **Used in**: `connect()` (disconnected event handler) +- **Source**: `content/wardrive.js` (disconnected event handler) - **Context**: Capacity check API returns error status or network is unreachable during connect -- **Minimum Visibility**: N/A (error state persists; message is preserved during automatic disconnect) -- **Notes**: Implements fail-closed policy - connection is denied if API fails or is unreachable. When this error occurs, the automatic disconnect flow preserves this status message instead of showing "Disconnected". Message format standardized with "Disconnected: " prefix to clearly indicate disconnect state. +- **Minimum Visibility**: N/A (error state persists as terminal status) +- **Notes**: Implements fail-closed policy - connection is denied if API fails or is unreachable. The complete sequence is: "Connecting" → "Acquiring wardriving slot" → "Disconnecting" → "Disconnected: WarDriving app is down". Message format standardized with "Disconnected: " prefix to clearly indicate disconnect state. #### Unable to read device public key; try again - **Message**: `"Unable to read device public key; try again"` From 987cab0a9624736ee3720a8c0e4fd87077e6cb99 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 05:00:25 +0000 Subject: [PATCH 36/47] Update comment to reflect new status message flow Co-authored-by: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com> --- content/wardrive.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/wardrive.js b/content/wardrive.js index b2f7014..c6fe8eb 100644 --- a/content/wardrive.js +++ b/content/wardrive.js @@ -2072,9 +2072,9 @@ async function connect() { const allowed = await checkCapacity("connect"); if (!allowed) { debugWarn("Capacity check denied, disconnecting"); - // Status message already set by checkCapacity() // disconnectReason already set by checkCapacity() - // Disconnect after a brief delay to ensure user sees the message + // Status message will be set by disconnected event handler based on disconnectReason + // Disconnect after a brief delay to ensure "Acquiring wardriving slot" is visible setTimeout(() => { disconnect().catch(err => debugError(`Disconnect after capacity denial failed: ${err.message}`)); }, 1500); From 12982de8596a7063ce1a164067944be854dfea83 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 05:00:37 +0000 Subject: [PATCH 37/47] Initial plan From 926b6198746b16447e5e1b0390829538d97f6e73 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 05:06:49 +0000 Subject: [PATCH 38/47] Initial plan From 42dc407c3f2d7d27d45126a4b52b8f274b60b22a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 05:07:06 +0000 Subject: [PATCH 39/47] Fix ping controls unlocking prematurely before API post completes - Add state.pingInProgress flag to track full ping lifecycle - Lock ping controls when ping operation starts (after validation) - Unlock controls only after API post completes in postApiAndRefreshMap - Add unlock logic for error paths and edge cases - Add debug logging for control lock/unlock events - Ensure controls remain locked during: ping send, RX listening, finalizing repeats, and API posting Co-authored-by: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com> --- content/wardrive.js | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/content/wardrive.js b/content/wardrive.js index dddca09..0bbbec4 100644 --- a/content/wardrive.js +++ b/content/wardrive.js @@ -130,6 +130,7 @@ const state = { capturedPingCoords: null, // { lat, lon, accuracy } captured at ping time, used for API post after 7s delay devicePublicKey: null, // Hex string of device's public key (used for capacity check) disconnectReason: null, // Tracks the reason for disconnection (e.g., "app_down", "capacity_full", "error", "normal") + pingInProgress: false, // Flag to track if a ping operation (including API post) is in progress repeaterTracking: { isListening: false, // Whether we're currently listening for echoes sentTimestamp: null, // Timestamp when the ping was sent @@ -425,8 +426,10 @@ function startCooldown() { function updateControlsForCooldown() { const connected = !!state.connection; const inCooldown = isInCooldown(); - sendPingBtn.disabled = !connected || inCooldown; - autoToggleBtn.disabled = !connected || inCooldown; + const pingInProgress = state.pingInProgress; + debugLog(`updateControlsForCooldown: connected=${connected}, inCooldown=${inCooldown}, pingInProgress=${pingInProgress}`); + sendPingBtn.disabled = !connected || inCooldown || pingInProgress; + autoToggleBtn.disabled = !connected || inCooldown || pingInProgress; } // Timer cleanup @@ -461,6 +464,9 @@ function cleanupAllTimers() { // Clear captured ping coordinates state.capturedPingCoords = null; + // Clear ping in progress flag + state.pingInProgress = false; + // Clear device public key state.devicePublicKey = null; } @@ -1190,6 +1196,11 @@ async function postApiAndRefreshMap(lat, lon, accuracy, heardRepeats) { debugLog(`Skipping map refresh (accuracy ${accuracy}m exceeds threshold)`); } + // Unlock ping controls now that API post is complete + state.pingInProgress = false; + updateControlsForCooldown(); + debugLog("Ping controls unlocked (pingInProgress=false) after API post completion"); + // Update status based on current mode if (state.connection) { if (state.running) { @@ -1834,6 +1845,11 @@ async function sendPing(manual = false) { // Both validations passed - execute ping operation (Mesh + API) debugLog("All validations passed, executing ping operation"); + // Lock ping controls for the entire ping lifecycle (until API post completes) + state.pingInProgress = true; + updateControlsForCooldown(); + debugLog("Ping controls locked (pingInProgress=true)"); + const payload = buildPayload(lat, lon); debugLog(`Sending ping to channel: "${payload}"`); @@ -1904,6 +1920,11 @@ async function sendPing(manual = false) { // This should never happen as coordinates are always captured before ping debugError(`CRITICAL: No captured ping coordinates available for API post - this indicates a logic error`); debugError(`Skipping API post to avoid posting incorrect coordinates`); + + // Unlock ping controls since API post is being skipped + state.pingInProgress = false; + updateControlsForCooldown(); + debugLog("Ping controls unlocked (pingInProgress=false) after skipping API post due to missing coordinates"); } // Clear captured coordinates after API post completes (always, regardless of path) @@ -1919,6 +1940,11 @@ async function sendPing(manual = false) { } catch (e) { debugError(`Ping operation failed: ${e.message}`, e); setStatus(e.message || "Ping failed", STATUS_COLORS.error); + + // Unlock ping controls on error + state.pingInProgress = false; + updateControlsForCooldown(); + debugLog("Ping controls unlocked (pingInProgress=false) after error"); } } From f9547778ed8e5143c42259f954740c874106ff36 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 05:08:06 +0000 Subject: [PATCH 40/47] Update CONNECTION_WORKFLOW.md to document ping control locking behavior - Add details about control locking during entire ping lifecycle - Document when controls lock/unlock relative to API posting - Clarify that controls remain locked through all phases including API post Co-authored-by: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com> --- docs/CONNECTION_WORKFLOW.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/CONNECTION_WORKFLOW.md b/docs/CONNECTION_WORKFLOW.md index 288a144..9e6f97c 100644 --- a/docs/CONNECTION_WORKFLOW.md +++ b/docs/CONNECTION_WORKFLOW.md @@ -523,6 +523,10 @@ stateDiagram-v2 ### Auto-Ping Interactions - **Manual during auto**: auto pauses, resumes after - **7s cooldown**: prevents rapid-fire pings +- **Control locking**: "Send Ping" and "Start Auto Ping" buttons remain locked for entire ping lifecycle: + - Locked when: ping sent → listening for repeats (7s) → finalizing repeats → posting to API (3s + API time) + - Unlocked when: API post completes or error occurs + - Prevents starting new pings while previous ping is still processing - **Page hidden**: auto stops, must restart manually - **Cooldown bypass**: only on disconnect From d12735ffcd6e51f948be600ad1856d848b944e33 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 05:09:59 +0000 Subject: [PATCH 41/47] Refactor ping control unlocking to use helper function - Create unlockPingControls(reason) helper to eliminate code duplication - Remove redundant pingInProgress variable in updateControlsForCooldown - Use helper function in all unlock paths for consistency - Improves maintainability and ensures consistent behavior Co-authored-by: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com> --- content/wardrive.js | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/content/wardrive.js b/content/wardrive.js index 0bbbec4..8a4ecb7 100644 --- a/content/wardrive.js +++ b/content/wardrive.js @@ -426,10 +426,19 @@ function startCooldown() { function updateControlsForCooldown() { const connected = !!state.connection; const inCooldown = isInCooldown(); - const pingInProgress = state.pingInProgress; - debugLog(`updateControlsForCooldown: connected=${connected}, inCooldown=${inCooldown}, pingInProgress=${pingInProgress}`); - sendPingBtn.disabled = !connected || inCooldown || pingInProgress; - autoToggleBtn.disabled = !connected || inCooldown || pingInProgress; + debugLog(`updateControlsForCooldown: connected=${connected}, inCooldown=${inCooldown}, pingInProgress=${state.pingInProgress}`); + sendPingBtn.disabled = !connected || inCooldown || state.pingInProgress; + autoToggleBtn.disabled = !connected || inCooldown || state.pingInProgress; +} + +/** + * Helper function to unlock ping controls after ping operation completes + * @param {string} reason - Debug reason for unlocking controls + */ +function unlockPingControls(reason) { + state.pingInProgress = false; + updateControlsForCooldown(); + debugLog(`Ping controls unlocked (pingInProgress=false) ${reason}`); } // Timer cleanup @@ -1197,9 +1206,7 @@ async function postApiAndRefreshMap(lat, lon, accuracy, heardRepeats) { } // Unlock ping controls now that API post is complete - state.pingInProgress = false; - updateControlsForCooldown(); - debugLog("Ping controls unlocked (pingInProgress=false) after API post completion"); + unlockPingControls("after API post completion"); // Update status based on current mode if (state.connection) { @@ -1922,9 +1929,7 @@ async function sendPing(manual = false) { debugError(`Skipping API post to avoid posting incorrect coordinates`); // Unlock ping controls since API post is being skipped - state.pingInProgress = false; - updateControlsForCooldown(); - debugLog("Ping controls unlocked (pingInProgress=false) after skipping API post due to missing coordinates"); + unlockPingControls("after skipping API post due to missing coordinates"); } // Clear captured coordinates after API post completes (always, regardless of path) @@ -1942,9 +1947,7 @@ async function sendPing(manual = false) { setStatus(e.message || "Ping failed", STATUS_COLORS.error); // Unlock ping controls on error - state.pingInProgress = false; - updateControlsForCooldown(); - debugLog("Ping controls unlocked (pingInProgress=false) after error"); + unlockPingControls("after error"); } } From 665cf984f2595e90c06c7fce00e9ef5b4cac57f9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 05:13:01 +0000 Subject: [PATCH 42/47] Fix status message ordering by removing duplicate setStatus call Remove setStatus("Disconnected") from final else block in disconnected event handler. This was causing the terminal error messages (capacity_full, app_down) to be overwritten. Added comprehensive debug logging to track disconnect reason and code paths. Co-authored-by: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com> --- content/wardrive.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/content/wardrive.js b/content/wardrive.js index c6fe8eb..da77dc5 100644 --- a/content/wardrive.js +++ b/content/wardrive.js @@ -2105,24 +2105,30 @@ async function connect() { conn.on("disconnected", () => { debugLog("BLE disconnected event fired"); + debugLog(`Disconnect reason: ${state.disconnectReason}`); // Set appropriate status message based on disconnect reason if (state.disconnectReason === "capacity_full") { + debugLog("Branch: capacity_full"); setStatus("Disconnected: WarDriving app has reached capacity", STATUS_COLORS.error); debugLog("Setting terminal status for capacity full"); } else if (state.disconnectReason === "app_down") { + debugLog("Branch: app_down"); setStatus("Disconnected: WarDriving app is down", STATUS_COLORS.error); debugLog("Setting terminal status for app down"); } else if (state.disconnectReason === "slot_revoked") { + debugLog("Branch: slot_revoked"); // For slot revocation, set the terminal status message setStatus("Disconnected: WarDriving slot has been revoked", STATUS_COLORS.error); debugLog("Setting terminal status for slot revocation"); } else if (state.disconnectReason === "normal" || state.disconnectReason === null || state.disconnectReason === undefined) { + debugLog("Branch: normal/null/undefined"); setStatus("Disconnected", STATUS_COLORS.error); } else { - // For "error" or any other disconnect reason, preserve the current status or show generic disconnected - debugLog(`Preserving disconnect status for reason: ${state.disconnectReason}`); - setStatus("Disconnected", STATUS_COLORS.error); + debugLog(`Branch: else (unknown reason: ${state.disconnectReason})`); + // For "error" disconnect reason, preserve the existing error status message + // Do NOT call setStatus here to avoid overwriting the error message + debugLog(`Preserving existing status for disconnect reason: ${state.disconnectReason}`); } setConnectButton(false); From 1242c94db3affaa9211316bc49d634dfce374bca Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 05:15:02 +0000 Subject: [PATCH 43/47] Update comment to clarify unknown disconnect reason handling Co-authored-by: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com> --- content/wardrive.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/wardrive.js b/content/wardrive.js index da77dc5..38db94b 100644 --- a/content/wardrive.js +++ b/content/wardrive.js @@ -2126,8 +2126,8 @@ async function connect() { setStatus("Disconnected", STATUS_COLORS.error); } else { debugLog(`Branch: else (unknown reason: ${state.disconnectReason})`); - // For "error" disconnect reason, preserve the existing error status message - // Do NOT call setStatus here to avoid overwriting the error message + // For unknown disconnect reasons (including "error"), preserve the existing status message + // Do NOT call setStatus here to avoid overwriting the message debugLog(`Preserving existing status for disconnect reason: ${state.disconnectReason}`); } From 40b7cea16a5ab44b78f6ab4ca368957c7e79aa10 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 05:17:36 +0000 Subject: [PATCH 44/47] Add specific disconnect reasons for error cases Replace generic "error" disconnect reason with specific reasons: - "public_key_error": When device public key is invalid or missing - "channel_setup_error": When channel setup or GPS initialization fails - "ble_disconnect_error": When BLE disconnect operation fails Each error now displays a specific terminal status message in the disconnected event handler, following the same pattern as capacity_full, app_down, and slot_revoked. The else block now shows generic "Disconnected" message for truly unknown reasons instead of trying to preserve non-existent status messages. Co-authored-by: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com> --- content/wardrive.js | 42 ++++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/content/wardrive.js b/content/wardrive.js index 38db94b..4b4c523 100644 --- a/content/wardrive.js +++ b/content/wardrive.js @@ -129,7 +129,9 @@ const state = { distanceUpdateTimer: null, // Timer for updating distance display capturedPingCoords: null, // { lat, lon, accuracy } captured at ping time, used for API post after 7s delay devicePublicKey: null, // Hex string of device's public key (used for capacity check) - disconnectReason: null, // Tracks the reason for disconnection (e.g., "app_down", "capacity_full", "error", "normal") + disconnectReason: null, // Tracks the reason for disconnection (e.g., "app_down", "capacity_full", "public_key_error", "channel_setup_error", "ble_disconnect_error", "normal") + channelSetupErrorMessage: null, // Error message from channel setup failure + bleDisconnectErrorMessage: null, // Error message from BLE disconnect failure repeaterTracking: { isListening: false, // Whether we're currently listening for echoes sentTimestamp: null, // Timestamp when the ping was sent @@ -2046,9 +2048,8 @@ async function connect() { // Validate and store public key if (!selfInfo?.publicKey || selfInfo.publicKey.length !== 32) { debugError("Missing or invalid public key from device", selfInfo?.publicKey); - setStatus("Unable to read device public key; try again", STATUS_COLORS.error); - state.disconnectReason = "error"; // Mark as error disconnect - // Disconnect after a brief delay to ensure user sees the error message + state.disconnectReason = "public_key_error"; // Mark specific disconnect reason + // Disconnect after a brief delay to ensure "Acquiring wardriving slot" is visible setTimeout(() => { disconnect().catch(err => debugError(`Disconnect after public key error failed: ${err.message}`)); }, 1500); @@ -2098,8 +2099,8 @@ async function connect() { debugLog("Full connection process completed successfully"); } catch (e) { debugError(`Channel setup failed: ${e.message}`, e); - setStatus(e.message || "Channel setup failed", STATUS_COLORS.error); - state.disconnectReason = "error"; // Mark as error disconnect + state.disconnectReason = "channel_setup_error"; // Mark specific disconnect reason + state.channelSetupErrorMessage = e.message || "Channel setup failed"; // Store error message } }); @@ -2118,17 +2119,32 @@ async function connect() { debugLog("Setting terminal status for app down"); } else if (state.disconnectReason === "slot_revoked") { debugLog("Branch: slot_revoked"); - // For slot revocation, set the terminal status message setStatus("Disconnected: WarDriving slot has been revoked", STATUS_COLORS.error); debugLog("Setting terminal status for slot revocation"); + } else if (state.disconnectReason === "public_key_error") { + debugLog("Branch: public_key_error"); + setStatus("Disconnected: Unable to read device public key", STATUS_COLORS.error); + debugLog("Setting terminal status for public key error"); + } else if (state.disconnectReason === "channel_setup_error") { + debugLog("Branch: channel_setup_error"); + const errorMsg = state.channelSetupErrorMessage || "Channel setup failed"; + setStatus(`Disconnected: ${errorMsg}`, STATUS_COLORS.error); + debugLog("Setting terminal status for channel setup error"); + state.channelSetupErrorMessage = null; // Clear after use + } else if (state.disconnectReason === "ble_disconnect_error") { + debugLog("Branch: ble_disconnect_error"); + const errorMsg = state.bleDisconnectErrorMessage || "BLE disconnect failed"; + setStatus(`Disconnected: ${errorMsg}`, STATUS_COLORS.error); + debugLog("Setting terminal status for BLE disconnect error"); + state.bleDisconnectErrorMessage = null; // Clear after use } else if (state.disconnectReason === "normal" || state.disconnectReason === null || state.disconnectReason === undefined) { debugLog("Branch: normal/null/undefined"); setStatus("Disconnected", STATUS_COLORS.error); } else { debugLog(`Branch: else (unknown reason: ${state.disconnectReason})`); - // For unknown disconnect reasons (including "error"), preserve the existing status message - // Do NOT call setStatus here to avoid overwriting the message - debugLog(`Preserving existing status for disconnect reason: ${state.disconnectReason}`); + // For unknown disconnect reasons, show generic disconnected message + debugLog(`Showing generic disconnected message for unknown reason: ${state.disconnectReason}`); + setStatus("Disconnected", STATUS_COLORS.error); } setConnectButton(false); @@ -2137,6 +2153,8 @@ async function connect() { state.channel = null; state.devicePublicKey = null; // Clear public key state.disconnectReason = null; // Reset disconnect reason + state.channelSetupErrorMessage = null; // Clear error message + state.bleDisconnectErrorMessage = null; // Clear error message stopAutoPing(true); // Ignore cooldown check on disconnect enableControls(false); updateAutoButton(); @@ -2217,8 +2235,8 @@ async function disconnect() { } } catch (e) { debugError(`BLE disconnect failed: ${e.message}`, e); - setStatus(e.message || "Disconnect failed", STATUS_COLORS.error); - state.disconnectReason = "error"; // Mark as error disconnect + state.disconnectReason = "ble_disconnect_error"; // Mark specific disconnect reason + state.bleDisconnectErrorMessage = e.message || "Disconnect failed"; // Store error message } finally { connectBtn.disabled = false; } From fc3042380ec9117fd802b9524ac3790e84ca74bb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 05:19:23 +0000 Subject: [PATCH 45/47] Improve comments and clarify duplicate clearing logic - Clarify disconnect delay comment to explain it ensures "Acquiring wardriving slot" status is visible before "Disconnecting" appears - Add comments to explain intentional duplicate clearing of error messages (in handler for immediate cleanup, in general cleanup as safety net) Co-authored-by: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com> --- content/wardrive.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/content/wardrive.js b/content/wardrive.js index 4b4c523..c3c5ee8 100644 --- a/content/wardrive.js +++ b/content/wardrive.js @@ -2049,7 +2049,8 @@ async function connect() { if (!selfInfo?.publicKey || selfInfo.publicKey.length !== 32) { debugError("Missing or invalid public key from device", selfInfo?.publicKey); state.disconnectReason = "public_key_error"; // Mark specific disconnect reason - // Disconnect after a brief delay to ensure "Acquiring wardriving slot" is visible + // Disconnect after a brief delay to ensure "Acquiring wardriving slot" status is visible + // before the disconnect sequence begins with "Disconnecting" setTimeout(() => { disconnect().catch(err => debugError(`Disconnect after public key error failed: ${err.message}`)); }, 1500); @@ -2130,13 +2131,13 @@ async function connect() { const errorMsg = state.channelSetupErrorMessage || "Channel setup failed"; setStatus(`Disconnected: ${errorMsg}`, STATUS_COLORS.error); debugLog("Setting terminal status for channel setup error"); - state.channelSetupErrorMessage = null; // Clear after use + state.channelSetupErrorMessage = null; // Clear after use (also cleared in cleanup as safety net) } else if (state.disconnectReason === "ble_disconnect_error") { debugLog("Branch: ble_disconnect_error"); const errorMsg = state.bleDisconnectErrorMessage || "BLE disconnect failed"; setStatus(`Disconnected: ${errorMsg}`, STATUS_COLORS.error); debugLog("Setting terminal status for BLE disconnect error"); - state.bleDisconnectErrorMessage = null; // Clear after use + state.bleDisconnectErrorMessage = null; // Clear after use (also cleared in cleanup as safety net) } else if (state.disconnectReason === "normal" || state.disconnectReason === null || state.disconnectReason === undefined) { debugLog("Branch: normal/null/undefined"); setStatus("Disconnected", STATUS_COLORS.error); From 48186ab97e1c0f51a6f15fed1bf8f395fd345bd1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 05:23:45 +0000 Subject: [PATCH 46/47] Initial plan From a602eeab675f9d6a04b0bb16dd5aa14c239cedb2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 05:27:02 +0000 Subject: [PATCH 47/47] Fix disconnected messages being queued - add immediate flag All "Disconnected" status messages now bypass the 500ms minimum visibility enforcement by passing `immediate: true` as the third parameter to setStatus. This ensures disconnected messages appear immediately without delay, fixing the issue where messages were queued with 445ms delays. Co-authored-by: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com> --- content/wardrive.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/content/wardrive.js b/content/wardrive.js index 0747e99..ecbbcf1 100644 --- a/content/wardrive.js +++ b/content/wardrive.js @@ -2140,40 +2140,40 @@ async function connect() { // Set appropriate status message based on disconnect reason if (state.disconnectReason === "capacity_full") { debugLog("Branch: capacity_full"); - setStatus("Disconnected: WarDriving app has reached capacity", STATUS_COLORS.error); + setStatus("Disconnected: WarDriving app has reached capacity", STATUS_COLORS.error, true); debugLog("Setting terminal status for capacity full"); } else if (state.disconnectReason === "app_down") { debugLog("Branch: app_down"); - setStatus("Disconnected: WarDriving app is down", STATUS_COLORS.error); + setStatus("Disconnected: WarDriving app is down", STATUS_COLORS.error, true); debugLog("Setting terminal status for app down"); } else if (state.disconnectReason === "slot_revoked") { debugLog("Branch: slot_revoked"); - setStatus("Disconnected: WarDriving slot has been revoked", STATUS_COLORS.error); + setStatus("Disconnected: WarDriving slot has been revoked", STATUS_COLORS.error, true); debugLog("Setting terminal status for slot revocation"); } else if (state.disconnectReason === "public_key_error") { debugLog("Branch: public_key_error"); - setStatus("Disconnected: Unable to read device public key", STATUS_COLORS.error); + setStatus("Disconnected: Unable to read device public key", STATUS_COLORS.error, true); debugLog("Setting terminal status for public key error"); } else if (state.disconnectReason === "channel_setup_error") { debugLog("Branch: channel_setup_error"); const errorMsg = state.channelSetupErrorMessage || "Channel setup failed"; - setStatus(`Disconnected: ${errorMsg}`, STATUS_COLORS.error); + setStatus(`Disconnected: ${errorMsg}`, STATUS_COLORS.error, true); debugLog("Setting terminal status for channel setup error"); state.channelSetupErrorMessage = null; // Clear after use (also cleared in cleanup as safety net) } else if (state.disconnectReason === "ble_disconnect_error") { debugLog("Branch: ble_disconnect_error"); const errorMsg = state.bleDisconnectErrorMessage || "BLE disconnect failed"; - setStatus(`Disconnected: ${errorMsg}`, STATUS_COLORS.error); + setStatus(`Disconnected: ${errorMsg}`, STATUS_COLORS.error, true); debugLog("Setting terminal status for BLE disconnect error"); state.bleDisconnectErrorMessage = null; // Clear after use (also cleared in cleanup as safety net) } else if (state.disconnectReason === "normal" || state.disconnectReason === null || state.disconnectReason === undefined) { debugLog("Branch: normal/null/undefined"); - setStatus("Disconnected", STATUS_COLORS.error); + setStatus("Disconnected", STATUS_COLORS.error, true); } else { debugLog(`Branch: else (unknown reason: ${state.disconnectReason})`); // For unknown disconnect reasons, show generic disconnected message debugLog(`Showing generic disconnected message for unknown reason: ${state.disconnectReason}`); - setStatus("Disconnected", STATUS_COLORS.error); + setStatus("Disconnected", STATUS_COLORS.error, true); } setConnectButton(false);