Skip to content

Commit dd68a69

Browse files
CopilotMrAlders0n
andcommitted
Implement slot availability/permission check for wardriving throttle
Co-authored-by: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com>
1 parent dd3c594 commit dd68a69

1 file changed

Lines changed: 128 additions & 1 deletion

File tree

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)