Skip to content

Commit 04c2006

Browse files
committed
Refactor capacity check to authentication request and update related logic for session management
1 parent 2f918a3 commit 04c2006

1 file changed

Lines changed: 156 additions & 76 deletions

File tree

content/wardrive.js

Lines changed: 156 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,8 @@ const API_TX_FLUSH_DELAY_MS = 3000; // Flush 3 seconds after TX ping
126126

127127
// MeshMapper API Configuration
128128
const MESHMAPPER_API_URL = "https://yow.meshmapper.net/wardriving-api.php";
129-
const MESHMAPPER_CAPACITY_CHECK_URL = "https://yow.meshmapper.net/capacitycheck.php";
130129
const GEO_AUTH_STATUS_URL = "https://meshmapper.net/wardrive-api.php/status"; // Geo-auth zone status endpoint
130+
const GEO_AUTH_URL = "https://meshmapper.net/wardrive-api.php/auth"; // Geo-auth connect/disconnect endpoint
131131
const MESHMAPPER_API_KEY = "59C7754DABDF5C11CA5F5D8368F89";
132132
const MESHMAPPER_DEFAULT_WHO = "GOME-WarDriver"; // Default identifier
133133
const MESHMAPPER_RX_LOG_API_URL = "https://yow.meshmapper.net/wardriving-api.php";
@@ -141,11 +141,19 @@ const WARDIVE_IATA_CODE = "YOW";
141141
// For DEV builds: Contains "DEV-<EPOCH>" format (e.g., "DEV-1734652800")
142142
const APP_VERSION = "UNKNOWN"; // Placeholder - replaced during build
143143

144-
// ---- Capacity Check Reason Messages ----
144+
// ---- Auth Reason Messages ----
145145
// Maps API reason codes to user-facing error messages
146146
const REASON_MESSAGES = {
147147
outofdate: "App out of date, please update",
148-
// Future reasons can be added here
148+
unknown_device: "Unknown device - advertise on mesh first",
149+
outside_zone: "Outside zone - cannot connect",
150+
zone_disabled: "Zone is disabled",
151+
zone_full: "TX slots full - RX only",
152+
bad_key: "Invalid API key",
153+
gps_stale: "GPS data too old - try again",
154+
gps_inaccurate: "GPS accuracy too low - try again",
155+
bad_session: "Invalid session",
156+
session_expired: "Session expired - reconnect",
149157
};
150158

151159
// ---- UI helpers ----
@@ -269,16 +277,19 @@ const state = {
269277
pausedAutoTimerRemainingMs: null, // Remaining time when auto ping timer was paused by manual ping
270278
lastSuccessfulPingLocation: null, // { lat, lon } of the last successful ping (Mesh + API)
271279
capturedPingCoords: null, // { lat, lon, accuracy } captured at ping time, used for API post after 7s delay
272-
devicePublicKey: null, // Hex string of device's public key (used for capacity check)
280+
devicePublicKey: null, // Hex string of device's public key (used for auth)
273281
deviceModel: null, // Manufacturer/model string exposed by companion
274282
autoPowerSet: false, // Whether power was automatically set based on device model
275283
lastNoiseFloor: null, // Most recent noise floor read from companion (dBm) or 'ERR'
276284
noiseFloorUpdateTimer: null, // Timer for periodic noise floor updates (5s interval)
277285
deviceName: null,
278-
wardriveSessionId: null, // Session ID from capacity check API (used for all MeshMapper API posts)
286+
wardriveSessionId: null, // Session ID from /auth API (used for all MeshMapper API posts)
279287
debugMode: false, // Whether debug mode is enabled by MeshMapper API
288+
txAllowed: false, // Whether TX wardriving is permitted (from /auth response)
289+
rxAllowed: false, // Whether RX wardriving is permitted (from /auth response)
290+
sessionExpiresAt: null, // Unix timestamp when session expires (for heartbeat scheduling)
280291
tempTxRepeaterData: null, // Temporary storage for TX repeater debug data
281-
disconnectReason: null, // Tracks the reason for disconnection (e.g., "app_down", "capacity_full", "public_key_error", "channel_setup_error", "ble_disconnect_error", "session_id_error", "normal", or API reason codes like "outofdate")
292+
disconnectReason: null, // Tracks the reason for disconnection (e.g., "app_down", "unknown_device", "outside_zone", "zone_disabled", "channel_setup_error", "ble_disconnect_error", "session_id_error", "normal", or API reason codes like "outofdate")
282293
channelSetupErrorMessage: null, // Error message from channel setup failure
283294
bleDisconnectErrorMessage: null, // Error message from BLE disconnect failure
284295
pendingApiPosts: [], // Array of pending background API post promises
@@ -604,16 +615,17 @@ function updateControlsForCooldown() {
604615
const connected = !!state.connection;
605616
const inCooldown = isInCooldown();
606617
const powerSelected = getCurrentPowerSetting() !== "";
607-
debugLog(`[UI] updateControlsForCooldown: connected=${connected}, inCooldown=${inCooldown}, pingInProgress=${state.pingInProgress}, txRxAutoRunning=${state.txRxAutoRunning}, rxAutoRunning=${state.rxAutoRunning}, powerSelected=${powerSelected}`);
618+
debugLog(`[UI] updateControlsForCooldown: connected=${connected}, inCooldown=${inCooldown}, pingInProgress=${state.pingInProgress}, txRxAutoRunning=${state.txRxAutoRunning}, rxAutoRunning=${state.rxAutoRunning}, powerSelected=${powerSelected}, txAllowed=${state.txAllowed}, rxAllowed=${state.rxAllowed}`);
608619

609-
// TX Ping button - disabled during cooldown, ping in progress, OR when no power selected
610-
txPingBtn.disabled = !connected || inCooldown || state.pingInProgress || !powerSelected;
620+
// TX Ping button - requires TX permission, disabled during cooldown, ping in progress, OR when no power selected
621+
txPingBtn.disabled = !connected || !state.txAllowed || inCooldown || state.pingInProgress || !powerSelected;
611622

612-
// TX/RX Auto button - disabled during cooldown, ping in progress, when RX Auto running, OR when no power selected
613-
txRxAutoBtn.disabled = !connected || inCooldown || state.pingInProgress || state.rxAutoRunning || !powerSelected;
623+
// TX/RX Auto button - requires TX permission, disabled during cooldown, ping in progress, when RX Auto running, OR when no power selected
624+
txRxAutoBtn.disabled = !connected || !state.txAllowed || inCooldown || state.pingInProgress || state.rxAutoRunning || !powerSelected;
614625

615-
// RX Auto button - permanently disabled (backend API not ready)
616-
rxAutoBtn.disabled = true;
626+
// RX Auto button - enabled when connected with RX permission (including RX-only mode)
627+
// Disabled during TX/RX Auto mode (can't run both), and requires power selected
628+
rxAutoBtn.disabled = !connected || !state.rxAllowed || state.txRxAutoRunning || !powerSelected;
617629
}
618630

619631
/**
@@ -1936,14 +1948,15 @@ async function checkZoneStatus(coords) {
19361948
}
19371949

19381950
/**
1939-
* Check capacity / slot availability with MeshMapper API
1940-
* @param {string} reason - Either "connect" (acquire slot) or "disconnect" (release slot)
1951+
* Request authentication with MeshMapper geo-auth API
1952+
* Handles both connect (acquire session) and disconnect (release session)
1953+
* @param {string} reason - Either "connect" (acquire session) or "disconnect" (release session)
19411954
* @returns {Promise<boolean>} True if allowed to continue, false otherwise
19421955
*/
1943-
async function checkCapacity(reason) {
1956+
async function requestAuth(reason) {
19441957
// Validate public key exists
19451958
if (!state.devicePublicKey) {
1946-
debugError("[CAPACITY] checkCapacity called but no public key stored");
1959+
debugError("[AUTH] requestAuth called but no public key stored");
19471960
return reason === "connect" ? false : true; // Fail closed on connect, allow disconnect
19481961
}
19491962

@@ -1953,93 +1966,146 @@ async function checkCapacity(reason) {
19531966
}
19541967

19551968
try {
1969+
// Build base payload
19561970
const payload = {
19571971
key: MESHMAPPER_API_KEY,
19581972
public_key: state.devicePublicKey,
1959-
ver: APP_VERSION,
1960-
who: getDeviceIdentifier(),
1961-
ver: APP_VERSION,
19621973
reason: reason
19631974
};
19641975

1965-
debugLog(`[CAPACITY] Checking capacity: reason=${reason}, public_key=${state.devicePublicKey.substring(0, 16)}..., who=${payload.who}`);
1976+
// For connect: add device metadata and GPS coords
1977+
if (reason === "connect") {
1978+
// Acquire fresh GPS for auth
1979+
debugLog("[AUTH] Acquiring fresh GPS for auth request");
1980+
const coords = await getValidGpsForZoneCheck();
1981+
1982+
if (!coords) {
1983+
debugError("[AUTH] Failed to acquire GPS for auth");
1984+
state.disconnectReason = "gps_unavailable";
1985+
return false;
1986+
}
1987+
1988+
// Add device metadata (bound to session at auth time)
1989+
payload.who = getDeviceIdentifier();
1990+
payload.ver = APP_VERSION;
1991+
payload.power = getCurrentPowerSetting();
1992+
payload.iata = state.currentZone?.code || WARDIVE_IATA_CODE;
1993+
payload.model = state.deviceModel || "Unknown";
1994+
1995+
// Add GPS coords (use lng for API, internally we use lon)
1996+
payload.coords = {
1997+
lat: coords.lat,
1998+
lng: coords.lon, // Convert lon → lng for API
1999+
accuracy_m: coords.accuracy_m,
2000+
timestamp: coords.timestamp
2001+
};
2002+
2003+
debugLog(`[AUTH] Connect request: public_key=${state.devicePublicKey.substring(0, 16)}..., who=${payload.who}, iata=${payload.iata}`);
2004+
} else {
2005+
// For disconnect: add session_id
2006+
payload.session_id = state.wardriveSessionId;
2007+
debugLog(`[AUTH] Disconnect request: public_key=${state.devicePublicKey.substring(0, 16)}..., session_id=${payload.session_id}`);
2008+
}
19662009

1967-
const response = await fetch(MESHMAPPER_CAPACITY_CHECK_URL, {
2010+
const response = await fetch(GEO_AUTH_URL, {
19682011
method: "POST",
19692012
headers: { "Content-Type": "application/json" },
19702013
body: JSON.stringify(payload)
19712014
});
19722015

2016+
// Handle HTTP-level errors
19732017
if (!response.ok) {
1974-
debugError(`[CAPACITY] Capacity check API returned error status ${response.status}`);
2018+
debugError(`[AUTH] API returned error status ${response.status}`);
19752019
// Fail closed on network errors for connect
19762020
if (reason === "connect") {
1977-
debugError("[CAPACITY] Failing closed (denying connection) due to API error");
1978-
state.disconnectReason = "app_down"; // Track disconnect reason
2021+
debugError("[AUTH] Failing closed (denying connection) due to API error");
2022+
state.disconnectReason = "app_down";
19792023
return false;
19802024
}
19812025
return true; // Always allow disconnect to proceed
19822026
}
19832027

19842028
const data = await response.json();
1985-
debugLog(`[CAPACITY] Capacity check response: allowed=${data.allowed}, session_id=${data.session_id || 'missing'}, debug_mode=${data.debug_mode || 'not set'}, reason=${data.reason || 'none'}`);
1986-
1987-
// Handle capacity full vs. allowed cases separately
1988-
if (data.allowed === false && reason === "connect") {
1989-
// Check if a reason code is provided
1990-
if (data.reason) {
1991-
debugLog(`[CAPACITY] API returned reason code: ${data.reason}`);
1992-
state.disconnectReason = data.reason; // Store the reason code directly
1993-
} else {
1994-
state.disconnectReason = "capacity_full"; // Default to capacity_full
2029+
debugLog(`[AUTH] Response: success=${data.success}, tx_allowed=${data.tx_allowed}, rx_allowed=${data.rx_allowed}, session_id=${data.session_id || 'none'}, reason=${data.reason || 'none'}`);
2030+
2031+
// Handle connect response
2032+
if (reason === "connect") {
2033+
// Check for full denial
2034+
if (data.success === false) {
2035+
debugLog(`[AUTH] Connect denied: ${data.reason} - ${data.message || ''}`);
2036+
state.disconnectReason = data.reason || "auth_denied";
2037+
return false;
19952038
}
1996-
return false;
1997-
}
1998-
1999-
// For connect requests, validate session_id and check debug_mode
2000-
if (reason === "connect" && data.allowed === true) {
2039+
2040+
// Success - store session info
20012041
if (!data.session_id) {
2002-
debugError("[CAPACITY] Capacity check returned allowed=true but session_id is missing");
2003-
state.disconnectReason = "session_id_error"; // Track disconnect reason
2042+
debugError("[AUTH] Auth returned success=true but session_id is missing");
2043+
state.disconnectReason = "session_id_error";
20042044
return false;
20052045
}
20062046

2007-
// Store the session_id for use in MeshMapper API posts
2047+
// Store session data
20082048
state.wardriveSessionId = data.session_id;
2009-
debugLog(`[CAPACITY] Wardrive session ID received and stored: ${state.wardriveSessionId}`);
2049+
state.txAllowed = data.tx_allowed === true;
2050+
state.rxAllowed = data.rx_allowed === true;
2051+
state.sessionExpiresAt = data.expires_at || null;
2052+
2053+
debugLog(`[AUTH] Session acquired: id=${state.wardriveSessionId}, tx=${state.txAllowed}, rx=${state.rxAllowed}, expires=${state.sessionExpiresAt}`);
2054+
2055+
// Check for RX-only scenario (zone_full)
2056+
if (!state.txAllowed && state.rxAllowed) {
2057+
debugLog(`[AUTH] RX-only mode: TX slots full, reason=${data.reason}`);
2058+
// Don't set disconnectReason - this is a partial success
2059+
}
20102060

20112061
// Check for debug_mode flag (optional field)
20122062
if (data.debug_mode === 1) {
20132063
state.debugMode = true;
2014-
debugLog(`[CAPACITY] 🐛 DEBUG MODE ENABLED by API`);
2064+
debugLog(`[AUTH] 🐛 DEBUG MODE ENABLED by API`);
20152065
} else {
20162066
state.debugMode = false;
2017-
debugLog(`[CAPACITY] Debug mode NOT enabled`);
20182067
}
2068+
2069+
return true; // Success (full or RX-only)
20192070
}
20202071

2021-
// For disconnect requests, clear the session_id and debug mode
2072+
// Handle disconnect response
20222073
if (reason === "disconnect") {
2023-
if (state.wardriveSessionId) {
2024-
debugLog(`[CAPACITY] Clearing wardrive session ID on disconnect: ${state.wardriveSessionId}`);
2025-
state.wardriveSessionId = null;
2026-
}
2074+
// Clear session state regardless of server response
2075+
debugLog(`[AUTH] Clearing session state on disconnect`);
2076+
state.wardriveSessionId = null;
2077+
state.txAllowed = false;
2078+
state.rxAllowed = false;
2079+
state.sessionExpiresAt = null;
20272080
state.debugMode = false;
2028-
debugLog(`[CAPACITY] Debug mode cleared on disconnect`);
2081+
2082+
if (data.success === true && data.disconnected === true) {
2083+
debugLog(`[AUTH] Disconnect confirmed by server`);
2084+
} else if (data.success === false) {
2085+
debugWarn(`[AUTH] Server reported disconnect error: ${data.reason} - ${data.message || ''}`);
2086+
// Don't fail - we still clean up locally
2087+
}
2088+
2089+
return true; // Always return true for disconnect
20292090
}
2030-
2031-
return data.allowed === true;
20322091

20332092
} catch (error) {
2034-
debugError(`[CAPACITY] Capacity check failed: ${error.message}`);
2093+
debugError(`[AUTH] Request failed: ${error.message}`);
20352094

20362095
// Fail closed on network errors for connect
20372096
if (reason === "connect") {
2038-
debugError("[CAPACITY] Failing closed (denying connection) due to network error");
2039-
state.disconnectReason = "app_down"; // Track disconnect reason
2097+
debugError("[AUTH] Failing closed (denying connection) due to network error");
2098+
state.disconnectReason = "app_down";
20402099
return false;
20412100
}
20422101

2102+
// For disconnect, clear state even on error
2103+
state.wardriveSessionId = null;
2104+
state.txAllowed = false;
2105+
state.rxAllowed = false;
2106+
state.sessionExpiresAt = null;
2107+
state.debugMode = false;
2108+
20432109
return true; // Always allow disconnect to proceed
20442110
}
20452111
}
@@ -5226,22 +5292,29 @@ async function connect() {
52265292
debugLog("[BLE] Device time sync not available or failed");
52275293
}
52285294
try {
5229-
// Check capacity immediately after time sync, before channel setup and GPS init
5230-
const allowed = await checkCapacity("connect");
5295+
// Request auth immediately after time sync, before channel setup and GPS init
5296+
// Note: requestAuth acquires fresh GPS internally
5297+
const allowed = await requestAuth("connect");
52315298
if (!allowed) {
5232-
debugWarn("[CAPACITY] Capacity check denied, disconnecting");
5233-
// disconnectReason already set by checkCapacity()
5299+
debugWarn("[AUTH] Auth request denied, disconnecting");
5300+
// disconnectReason already set by requestAuth()
52345301
// Status message will be set by disconnected event handler based on disconnectReason
52355302
// Disconnect after a brief delay to ensure "Acquiring wardriving slot" is visible
52365303
setTimeout(() => {
5237-
disconnect().catch(err => debugError(`[BLE] Disconnect after capacity denial failed: ${err.message}`));
5304+
disconnect().catch(err => debugError(`[BLE] Disconnect after auth denial failed: ${err.message}`));
52385305
}, 1500);
52395306
return;
52405307
}
52415308

5242-
// Capacity check passed
5243-
setDynamicStatus("Acquired wardriving slot", STATUS_COLORS.success);
5244-
debugLog("[BLE] Wardriving slot acquired successfully");
5309+
// Auth passed - check if full access or RX-only
5310+
if (state.txAllowed && state.rxAllowed) {
5311+
setDynamicStatus("Acquired wardriving slot", STATUS_COLORS.success);
5312+
debugLog("[AUTH] Full access granted (TX + RX)");
5313+
} else if (state.rxAllowed) {
5314+
setDynamicStatus("TX slots full - RX only", STATUS_COLORS.warning);
5315+
debugLog("[AUTH] RX-only access granted (TX slots full)");
5316+
}
5317+
debugLog(`[AUTH] Session acquired: tx=${state.txAllowed}, rx=${state.rxAllowed}`);
52455318

52465319
// Proceed with channel setup and GPS initialization
52475320
await ensureChannel();
@@ -5250,13 +5323,17 @@ async function connect() {
52505323
startUnifiedRxListening();
52515324
debugLog("[BLE] Unified RX listener started on connect");
52525325

5253-
// GPS initialization
5254-
debugLog("[BLE] Starting GPS initialization");
5326+
// GPS initialization (primeGpsOnce for watch mode)
5327+
// Note: Fresh GPS was already acquired by requestAuth, this starts continuous watching
5328+
debugLog("[BLE] Starting GPS watch mode");
52555329
await primeGpsOnce();
52565330

5257-
// Connection complete, show Connected status in connection bar
5258-
// Note: Zone validation will happen server-side via /auth endpoint (Phase 4.2)
5259-
setConnStatus("Connected", STATUS_COLORS.success);
5331+
// Connection complete - show status based on TX+RX vs RX-only
5332+
if (state.txAllowed && state.rxAllowed) {
5333+
setConnStatus("Connected", STATUS_COLORS.success);
5334+
} else if (state.rxAllowed) {
5335+
setConnStatus("Connected (RX Only)", STATUS_COLORS.warning);
5336+
}
52605337

52615338
// If device is unknown and power not selected, show warning message
52625339
if (!state.autoPowerSet && !getCurrentPowerSetting()) {
@@ -5385,6 +5462,9 @@ async function connect() {
53855462
state.channel = null;
53865463
state.devicePublicKey = null; // Clear public key
53875464
state.wardriveSessionId = null; // Clear wardrive session ID
5465+
state.txAllowed = false; // Clear TX permission
5466+
state.rxAllowed = false; // Clear RX permission
5467+
state.sessionExpiresAt = null; // Clear session expiration
53885468
state.debugMode = false; // Clear debug mode
53895469
state.tempTxRepeaterData = null; // Clear temp TX data
53905470
state.disconnectReason = null; // Reset disconnect reason
@@ -5498,14 +5578,14 @@ async function disconnect() {
54985578
}
54995579
stopFlushTimers();
55005580

5501-
// 3. THEN release capacity slot if we have a public key
5502-
if (state.devicePublicKey) {
5581+
// 3. THEN release session via auth API if we have a public key
5582+
if (state.devicePublicKey && state.wardriveSessionId) {
55035583
try {
5504-
debugLog("[BLE] Releasing capacity slot");
5505-
await checkCapacity("disconnect");
5584+
debugLog("[AUTH] Releasing session via /auth disconnect");
5585+
await requestAuth("disconnect");
55065586
} catch (e) {
5507-
debugWarn(`[CAPACITY] Failed to release capacity slot: ${e.message}`);
5508-
// Don't fail disconnect if capacity release fails
5587+
debugWarn(`[AUTH] Failed to release session: ${e.message}`);
5588+
// Don't fail disconnect if auth release fails
55095589
}
55105590
}
55115591

0 commit comments

Comments
 (0)