Skip to content

Commit 05cdc3b

Browse files
authored
Merge pull request #72 from MrAlders0n/copilot/implement-slot-availability-check
Implement slot availability check for wardriving throttle
2 parents 9aed5fe + aeaf0b2 commit 05cdc3b

2 files changed

Lines changed: 172 additions & 7 deletions

File tree

STATUS_MESSAGES.md

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,44 @@ Status messages follow these consistent conventions:
7979

8080
---
8181

82-
### 2. Ping Operation Messages
82+
### 2. Capacity Check Messages
83+
84+
#### Acquiring wardriving slot
85+
- **Message**: `"Acquiring wardriving slot"`
86+
- **Color**: Sky blue (info)
87+
- **Used in**: `checkCapacity()`
88+
- **Source**: `content/wardrive.js:1026`
89+
- **Context**: When connecting to device and checking if a wardriving slot is available
90+
- **Minimum Visibility**: 500ms minimum enforced (or until API response received)
91+
92+
#### WarDriving app has reached capacity or is down
93+
- **Message**: `"WarDriving app has reached capacity or is down"`
94+
- **Color**: Red (error)
95+
- **Used in**: `checkCapacity()`, `postToMeshMapperAPI()`
96+
- **Source**: `content/wardrive.js:2051`, `content/wardrive.js:1115`
97+
- **Context**: Capacity check API denies slot on connect, or wardriving API returns allowed=false during active session
98+
- **Minimum Visibility**: N/A (error state persists until disconnect)
99+
100+
#### Unable to read device public key; try again
101+
- **Message**: `"Unable to read device public key; try again"`
102+
- **Color**: Red (error)
103+
- **Used in**: `connect()`
104+
- **Source**: `content/wardrive.js:2023`
105+
- **Context**: Device public key is missing or invalid when trying to acquire capacity slot
106+
- **Minimum Visibility**: N/A (error state persists until disconnect)
107+
108+
#### Network issue checking slot, proceeding anyway
109+
- **Message**: `"Network issue checking slot, proceeding anyway"`
110+
- **Color**: Amber (warning)
111+
- **Used in**: `checkCapacity()`
112+
- **Source**: `content/wardrive.js:1051`, `content/wardrive.js:1070`
113+
- **Context**: Capacity check API is unreachable or returns error during connect (fail-open behavior)
114+
- **Minimum Visibility**: 1500ms enforced (brief warning before continuing)
115+
- **Notes**: Implements fail-open policy - allows connection to proceed despite API failure
116+
117+
---
118+
119+
### 3. Ping Operation Messages
83120

84121
#### Sending manual ping
85122
- **Message**: `"Sending manual ping"`
@@ -140,7 +177,7 @@ Status messages follow these consistent conventions:
140177

141178
---
142179

143-
### 3. GPS Status Messages
180+
### 4. GPS Status Messages
144181

145182
#### Waiting for GPS fix
146183
- **Message**: `"Waiting for GPS fix"`
@@ -160,7 +197,7 @@ Status messages follow these consistent conventions:
160197

161198
---
162199

163-
### 4. Countdown Timer Messages
200+
### 5. Countdown Timer Messages
164201

165202
These messages use a hybrid approach: **first display respects 500ms minimum**, then updates occur immediately every second.
166203

@@ -216,7 +253,7 @@ These messages use a hybrid approach: **first display respects 500ms minimum**,
216253

217254
---
218255

219-
### 5. API and Map Update Messages
256+
### 6. API and Map Update Messages
220257

221258
#### Posting to API
222259
- **Message**: `"Posting to API"`
@@ -238,7 +275,7 @@ These messages use a hybrid approach: **first display respects 500ms minimum**,
238275

239276
---
240277

241-
### 6. Auto Mode Messages
278+
### 7. Auto Mode Messages
242279

243280
#### Auto mode stopped
244281
- **Message**: `"Auto mode stopped"`
@@ -323,8 +360,9 @@ Result: "Message A" (visible 500ms) → "Message C"
323360

324361
## Summary
325362

326-
**Total Status Messages**: 25 unique message patterns
363+
**Total Status Messages**: 29 unique message patterns
327364
- **Connection**: 7 messages
365+
- **Capacity Check**: 4 messages
328366
- **Ping Operation**: 6 messages (consolidated "Ping sent" for both manual and auto)
329367
- **GPS**: 2 messages
330368
- **Countdown Timers**: 6 message patterns (with dynamic countdown values)

content/wardrive.js

Lines changed: 128 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// - Manual "Send Ping" and Auto mode (interval selectable: 15/30/60s)
66
// - Acquire wake lock during auto mode to keep screen awake
77

8-
import { WebBleConnection, Constants, Packet } from "./mc/index.js"; // your BLE client
8+
import { WebBleConnection, Constants, Packet, BufferUtils } from "./mc/index.js"; // your BLE client
99

1010
// ---- Debug Configuration ----
1111
// 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
7575

7676
// MeshMapper API Configuration
7777
const MESHMAPPER_API_URL = "https://yow.meshmapper.net/wardriving-api.php";
78+
const MESHMAPPER_CAPACITY_CHECK_URL = "https://yow.meshmapper.net/capacitycheck.php";
7879
const MESHMAPPER_API_KEY = "59C7754DABDF5C11CA5F5D8368F89";
7980
const MESHMAPPER_DEFAULT_WHO = "GOME-WarDriver"; // Default identifier
8081

@@ -121,6 +122,7 @@ const state = {
121122
lastSuccessfulPingLocation: null, // { lat, lon } of the last successful ping (Mesh + API)
122123
distanceUpdateTimer: null, // Timer for updating distance display
123124
capturedPingCoords: null, // { lat, lon, accuracy } captured at ping time, used for API post after 7s delay
125+
devicePublicKey: null, // Hex string of device's public key (used for capacity check)
124126
repeaterTracking: {
125127
isListening: false, // Whether we're currently listening for echoes
126128
sentTimestamp: null, // Timestamp when the ping was sent
@@ -451,6 +453,9 @@ function cleanupAllTimers() {
451453

452454
// Clear captured ping coordinates
453455
state.capturedPingCoords = null;
456+
457+
// Clear device public key
458+
state.devicePublicKey = null;
454459
}
455460

456461
function enableControls(connected) {
@@ -1004,6 +1009,73 @@ function getDeviceIdentifier() {
10041009
return (deviceText && deviceText !== "—") ? deviceText : MESHMAPPER_DEFAULT_WHO;
10051010
}
10061011

1012+
/**
1013+
* Check capacity / slot availability with MeshMapper API
1014+
* @param {string} reason - Either "connect" (acquire slot) or "disconnect" (release slot)
1015+
* @returns {Promise<boolean>} True if allowed to continue, false otherwise
1016+
*/
1017+
async function checkCapacity(reason) {
1018+
// Validate public key exists
1019+
if (!state.devicePublicKey) {
1020+
debugError("checkCapacity called but no public key stored");
1021+
return reason === "connect" ? false : true; // Fail closed on connect, allow disconnect
1022+
}
1023+
1024+
// Set status for connect requests
1025+
if (reason === "connect") {
1026+
setStatus("Acquiring wardriving slot", STATUS_COLORS.info);
1027+
}
1028+
1029+
try {
1030+
const payload = {
1031+
key: MESHMAPPER_API_KEY,
1032+
public_key: state.devicePublicKey,
1033+
who: getDeviceIdentifier(),
1034+
reason: reason
1035+
};
1036+
1037+
debugLog(`Checking capacity: reason=${reason}, public_key=${state.devicePublicKey.substring(0, 16)}..., who=${payload.who}`);
1038+
1039+
const response = await fetch(MESHMAPPER_CAPACITY_CHECK_URL, {
1040+
method: "POST",
1041+
headers: { "Content-Type": "application/json" },
1042+
body: JSON.stringify(payload)
1043+
});
1044+
1045+
if (!response.ok) {
1046+
debugWarn(`Capacity check API returned error status ${response.status}`);
1047+
// Fail open on network errors for connect
1048+
if (reason === "connect") {
1049+
debugWarn("Failing open (allowing connection) due to API error");
1050+
// Show network issue message briefly
1051+
setStatus("Network issue checking slot, proceeding anyway", STATUS_COLORS.warning);
1052+
await new Promise(resolve => setTimeout(resolve, 1500)); // Show message for 1.5s
1053+
return true;
1054+
}
1055+
return true; // Always allow disconnect to proceed
1056+
}
1057+
1058+
const data = await response.json();
1059+
debugLog(`Capacity check response: allowed=${data.allowed}`);
1060+
1061+
return data.allowed === true;
1062+
1063+
} catch (error) {
1064+
debugError(`Capacity check failed: ${error.message}`);
1065+
1066+
// Fail open on network errors for connect
1067+
if (reason === "connect") {
1068+
debugWarn("Failing open (allowing connection) due to network error");
1069+
// Show network issue message briefly
1070+
setStatus("Network issue checking slot, proceeding anyway", STATUS_COLORS.warning);
1071+
await new Promise(resolve => setTimeout(resolve, 1500)); // Show message for 1.5s
1072+
return true;
1073+
}
1074+
1075+
return true; // Always allow disconnect to proceed
1076+
}
1077+
}
1078+
10071079
/**
10081080
* Post wardrive ping data to MeshMapper API
10091081
* @param {number} lat - Latitude
@@ -1034,6 +1106,22 @@ async function postToMeshMapperAPI(lat, lon, heardRepeats) {
10341106
debugWarn(`MeshMapper API returned error status ${response.status}`);
10351107
} else {
10361108
debugLog(`MeshMapper API post successful (status ${response.status})`);
1109+
1110+
// Parse response and check if we're allowed to continue
1111+
try {
1112+
const data = await response.json();
1113+
if (data.allowed === false) {
1114+
debugWarn("MeshMapper API returned allowed=false, disconnecting");
1115+
setStatus("WarDriving app has reached capacity or is down", STATUS_COLORS.error);
1116+
// Disconnect after a brief delay to ensure user sees the message
1117+
setTimeout(() => {
1118+
disconnect().catch(err => debugError(`Disconnect after capacity denial failed: ${err.message}`));
1119+
}, 1500);
1120+
}
1121+
} catch (parseError) {
1122+
debugWarn(`Failed to parse MeshMapper API response: ${parseError.message}`);
1123+
// Continue operation if we can't parse the response
1124+
}
10371125
}
10381126
} catch (error) {
10391127
// Log error but don't fail the ping
@@ -1928,6 +2016,22 @@ async function connect() {
19282016
connectBtn.disabled = false;
19292017
const selfInfo = await conn.getSelfInfo();
19302018
debugLog(`Device info: ${selfInfo?.name || "[No device]"}`);
2019+
2020+
// Validate and store public key
2021+
if (!selfInfo?.publicKey || selfInfo.publicKey.length !== 32) {
2022+
debugError("Missing or invalid public key from device", selfInfo?.publicKey);
2023+
setStatus("Unable to read device public key; try again", STATUS_COLORS.error);
2024+
// Disconnect after a brief delay to ensure user sees the error message
2025+
setTimeout(() => {
2026+
disconnect().catch(err => debugError(`Disconnect after public key error failed: ${err.message}`));
2027+
}, 1500);
2028+
return;
2029+
}
2030+
2031+
// Convert public key to hex and store
2032+
state.devicePublicKey = BufferUtils.bytesToHex(selfInfo.publicKey);
2033+
debugLog(`Device public key stored: ${state.devicePublicKey.substring(0, 16)}...`);
2034+
19312035
deviceInfoEl.textContent = selfInfo?.name || "[No device]";
19322036
updateAutoButton();
19332037
try {
@@ -1939,6 +2043,17 @@ async function connect() {
19392043
try {
19402044
await ensureChannel();
19412045
await primeGpsOnce();
2046+
2047+
// Check capacity after GPS is initialized
2048+
const allowed = await checkCapacity("connect");
2049+
if (!allowed) {
2050+
debugWarn("Capacity check denied, disconnecting");
2051+
setStatus("WarDriving app has reached capacity or is down", STATUS_COLORS.error);
2052+
// Disconnect after a brief delay to ensure user sees the message
2053+
setTimeout(() => {
2054+
disconnect().catch(err => debugError(`Disconnect after capacity denial failed: ${err.message}`));
2055+
}, 1500);
2056+
}
19422057
} catch (e) {
19432058
debugError(`Channel setup failed: ${e.message}`, e);
19442059
setStatus(e.message || "Channel setup failed", STATUS_COLORS.error);
@@ -1952,6 +2067,7 @@ async function connect() {
19522067
deviceInfoEl.textContent = "—";
19532068
state.connection = null;
19542069
state.channel = null;
2070+
state.devicePublicKey = null; // Clear public key
19552071
stopAutoPing(true); // Ignore cooldown check on disconnect
19562072
enableControls(false);
19572073
updateAutoButton();
@@ -1987,6 +2103,17 @@ async function disconnect() {
19872103
connectBtn.disabled = true;
19882104
setStatus("Disconnecting", STATUS_COLORS.info);
19892105

2106+
// Release capacity slot if we have a public key
2107+
if (state.devicePublicKey) {
2108+
try {
2109+
debugLog("Releasing capacity slot");
2110+
await checkCapacity("disconnect");
2111+
} catch (e) {
2112+
debugWarn(`Failed to release capacity slot: ${e.message}`);
2113+
// Don't fail disconnect if capacity release fails
2114+
}
2115+
}
2116+
19902117
// Delete the wardriving channel before disconnecting
19912118
try {
19922119
if (state.channel && typeof state.connection.deleteChannel === "function") {

0 commit comments

Comments
 (0)