const http = require('http') const fs = require('fs') const path = require('path') const { WebSocketServer } = require('ws') const HOST = '0.0.0.0' const PORT = 17865 const WS_PATH = '/mock-gps' const PROXY_PATH = '/proxy' const PUBLIC_DIR = path.join(__dirname, 'public') function getContentType(filePath) { const ext = path.extname(filePath).toLowerCase() if (ext === '.html') { return 'text/html; charset=utf-8' } if (ext === '.css') { return 'text/css; charset=utf-8' } if (ext === '.js') { return 'application/javascript; charset=utf-8' } if (ext === '.json') { return 'application/json; charset=utf-8' } if (ext === '.svg') { return 'image/svg+xml' } return 'text/plain; charset=utf-8' } function serveStatic(requestPath, response) { const safePath = requestPath === '/' ? '/index.html' : requestPath const resolvedPath = path.normalize(path.join(PUBLIC_DIR, safePath)) if (!resolvedPath.startsWith(PUBLIC_DIR)) { response.writeHead(403) response.end('Forbidden') return } fs.readFile(resolvedPath, (error, content) => { if (error) { response.writeHead(404) response.end('Not Found') return } response.writeHead(200, { 'Content-Type': getContentType(resolvedPath), 'Cache-Control': 'no-store', }) response.end(content) }) } function isMockGpsPayload(payload) { return payload && payload.type === 'mock_gps' && Number.isFinite(payload.lat) && Number.isFinite(payload.lon) } async function handleProxyRequest(request, response) { const requestUrl = new URL(request.url || '/', `http://127.0.0.1:${PORT}`) const targetUrl = requestUrl.searchParams.get('url') if (!targetUrl) { response.writeHead(400, { 'Content-Type': 'text/plain; charset=utf-8', 'Access-Control-Allow-Origin': '*', }) response.end('Missing url') return } try { const upstream = await fetch(targetUrl) const body = Buffer.from(await upstream.arrayBuffer()) response.writeHead(upstream.status, { 'Content-Type': upstream.headers.get('content-type') || 'application/octet-stream', 'Cache-Control': 'no-store', 'Access-Control-Allow-Origin': '*', }) response.end(body) } catch (error) { response.writeHead(502, { 'Content-Type': 'text/plain; charset=utf-8', 'Access-Control-Allow-Origin': '*', }) response.end(error && error.message ? error.message : 'Proxy request failed') } } const server = http.createServer((request, response) => { if ((request.url || '').startsWith(PROXY_PATH)) { handleProxyRequest(request, response) return } serveStatic(request.url || '/', response) }) const wss = new WebSocketServer({ noServer: true }) wss.on('connection', (socket) => { socket.on('message', (rawMessage) => { const text = String(rawMessage) let parsed try { parsed = JSON.parse(text) } catch (_error) { return } if (!isMockGpsPayload(parsed)) { return } const serialized = JSON.stringify({ type: 'mock_gps', timestamp: Number.isFinite(parsed.timestamp) ? parsed.timestamp : Date.now(), lat: Number(parsed.lat), lon: Number(parsed.lon), accuracyMeters: Number.isFinite(parsed.accuracyMeters) ? Number(parsed.accuracyMeters) : 6, speedMps: Number.isFinite(parsed.speedMps) ? Number(parsed.speedMps) : 0, headingDeg: Number.isFinite(parsed.headingDeg) ? Number(parsed.headingDeg) : 0, }) wss.clients.forEach((client) => { if (client.readyState === client.OPEN) { client.send(serialized) } }) }) }) server.on('upgrade', (request, socket, head) => { if (!request.url || !request.url.startsWith(WS_PATH)) { socket.destroy() return } wss.handleUpgrade(request, socket, head, (ws) => { wss.emit('connection', ws, request) }) }) server.listen(PORT, HOST, () => { console.log(`Mock GPS simulator running:`) console.log(` UI: http://127.0.0.1:${PORT}/`) console.log(` WS: ws://127.0.0.1:${PORT}${WS_PATH}`) console.log(` Proxy: http://127.0.0.1:${PORT}${PROXY_PATH}?url=`) })