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
7777const MESHMAPPER_API_URL = "https://yow.meshmapper.net/wardriving-api.php" ;
78+ const MESHMAPPER_CAPACITY_CHECK_URL = "https://yow.meshmapper.net/capacitycheck.php" ;
7879const MESHMAPPER_API_KEY = "59C7754DABDF5C11CA5F5D8368F89" ;
7980const 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
456461function 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