server.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. const http = require('http')
  2. const fs = require('fs')
  3. const path = require('path')
  4. const { WebSocketServer } = require('ws')
  5. const HOST = '0.0.0.0'
  6. const PORT = 17865
  7. const WS_PATH = '/mock-gps'
  8. const PROXY_PATH = '/proxy'
  9. const PUBLIC_DIR = path.join(__dirname, 'public')
  10. function getContentType(filePath) {
  11. const ext = path.extname(filePath).toLowerCase()
  12. if (ext === '.html') {
  13. return 'text/html; charset=utf-8'
  14. }
  15. if (ext === '.css') {
  16. return 'text/css; charset=utf-8'
  17. }
  18. if (ext === '.js') {
  19. return 'application/javascript; charset=utf-8'
  20. }
  21. if (ext === '.json') {
  22. return 'application/json; charset=utf-8'
  23. }
  24. if (ext === '.svg') {
  25. return 'image/svg+xml'
  26. }
  27. return 'text/plain; charset=utf-8'
  28. }
  29. function serveStatic(requestPath, response) {
  30. const safePath = requestPath === '/' ? '/index.html' : requestPath
  31. const resolvedPath = path.normalize(path.join(PUBLIC_DIR, safePath))
  32. if (!resolvedPath.startsWith(PUBLIC_DIR)) {
  33. response.writeHead(403)
  34. response.end('Forbidden')
  35. return
  36. }
  37. fs.readFile(resolvedPath, (error, content) => {
  38. if (error) {
  39. response.writeHead(404)
  40. response.end('Not Found')
  41. return
  42. }
  43. response.writeHead(200, {
  44. 'Content-Type': getContentType(resolvedPath),
  45. 'Cache-Control': 'no-store',
  46. })
  47. response.end(content)
  48. })
  49. }
  50. function isMockGpsPayload(payload) {
  51. return payload
  52. && payload.type === 'mock_gps'
  53. && Number.isFinite(payload.lat)
  54. && Number.isFinite(payload.lon)
  55. }
  56. async function handleProxyRequest(request, response) {
  57. const requestUrl = new URL(request.url || '/', `http://127.0.0.1:${PORT}`)
  58. const targetUrl = requestUrl.searchParams.get('url')
  59. if (!targetUrl) {
  60. response.writeHead(400, {
  61. 'Content-Type': 'text/plain; charset=utf-8',
  62. 'Access-Control-Allow-Origin': '*',
  63. })
  64. response.end('Missing url')
  65. return
  66. }
  67. try {
  68. const upstream = await fetch(targetUrl)
  69. const body = Buffer.from(await upstream.arrayBuffer())
  70. response.writeHead(upstream.status, {
  71. 'Content-Type': upstream.headers.get('content-type') || 'application/octet-stream',
  72. 'Cache-Control': 'no-store',
  73. 'Access-Control-Allow-Origin': '*',
  74. })
  75. response.end(body)
  76. } catch (error) {
  77. response.writeHead(502, {
  78. 'Content-Type': 'text/plain; charset=utf-8',
  79. 'Access-Control-Allow-Origin': '*',
  80. })
  81. response.end(error && error.message ? error.message : 'Proxy request failed')
  82. }
  83. }
  84. const server = http.createServer((request, response) => {
  85. if ((request.url || '').startsWith(PROXY_PATH)) {
  86. handleProxyRequest(request, response)
  87. return
  88. }
  89. serveStatic(request.url || '/', response)
  90. })
  91. const wss = new WebSocketServer({ noServer: true })
  92. wss.on('connection', (socket) => {
  93. socket.on('message', (rawMessage) => {
  94. const text = String(rawMessage)
  95. let parsed
  96. try {
  97. parsed = JSON.parse(text)
  98. } catch (_error) {
  99. return
  100. }
  101. if (!isMockGpsPayload(parsed)) {
  102. return
  103. }
  104. const serialized = JSON.stringify({
  105. type: 'mock_gps',
  106. timestamp: Number.isFinite(parsed.timestamp) ? parsed.timestamp : Date.now(),
  107. lat: Number(parsed.lat),
  108. lon: Number(parsed.lon),
  109. accuracyMeters: Number.isFinite(parsed.accuracyMeters) ? Number(parsed.accuracyMeters) : 6,
  110. speedMps: Number.isFinite(parsed.speedMps) ? Number(parsed.speedMps) : 0,
  111. headingDeg: Number.isFinite(parsed.headingDeg) ? Number(parsed.headingDeg) : 0,
  112. })
  113. wss.clients.forEach((client) => {
  114. if (client.readyState === client.OPEN) {
  115. client.send(serialized)
  116. }
  117. })
  118. })
  119. })
  120. server.on('upgrade', (request, socket, head) => {
  121. if (!request.url || !request.url.startsWith(WS_PATH)) {
  122. socket.destroy()
  123. return
  124. }
  125. wss.handleUpgrade(request, socket, head, (ws) => {
  126. wss.emit('connection', ws, request)
  127. })
  128. })
  129. server.listen(PORT, HOST, () => {
  130. console.log(`Mock GPS simulator running:`)
  131. console.log(` UI: http://127.0.0.1:${PORT}/`)
  132. console.log(` WS: ws://127.0.0.1:${PORT}${WS_PATH}`)
  133. console.log(` Proxy: http://127.0.0.1:${PORT}${PROXY_PATH}?url=<remote-url>`)
  134. })