OverviewFilesCommitsIssuesDocsReleasesPeers

host-services / lib / server.js

64 lines · javascript ref:
// HTTP surface: the page, and a read-only JSON API.
//
//   GET  /api/host       this host's services (cached ~15 s)
//   GET  /api/lan        the last LAN scan + whether one is running
//   POST /api/lan/scan   start a LAN scan now
//
// ⛔ No login: LAN-only, like the services it lists. The one POST must carry
// `x-host-services: 1` -- a cross-site page cannot add a custom header without
// a CORS preflight, and this server never answers one.

import http from 'bare-http1'
import fs from 'bare-fs'
import path from 'bare-path'

const STATIC_DIR = path.join(path.dirname(new URL(import.meta.url).pathname), '..', 'static')
const ASSETS = {
  '/': ['index.html', 'text/html; charset=utf-8'],
  '/index.html': ['index.html', 'text/html; charset=utf-8'],
  '/app.js': ['app.js', 'text/javascript; charset=utf-8'],
  '/app.css': ['app.css', 'text/css; charset=utf-8']
}

function sendJSON (res, status, obj) {
  const body = JSON.stringify(obj)
  res.writeHead(status, { 'content-type': 'application/json', 'content-length': Buffer.byteLength(body), 'cache-control': 'no-store' })
  res.end(body)
}

export function createServer ({ host, lan }) {
  return http.createServer((req, res) => {
    const url = req.url.split('?')[0]

    if (url === '/api/host' && req.method === 'GET') {
      host.snapshot().then((s) => sendJSON(res, 200, s), (err) => {
        console.error('host snapshot failed: ' + (err.stack || err))
        sendJSON(res, 500, { error: err.message })
      })
      return
    }
    if (url === '/api/lan' && req.method === 'GET') return sendJSON(res, 200, lan.state())
    if (url === '/api/lan/scan' && req.method === 'POST') {
      if (req.headers['x-host-services'] !== '1') return sendJSON(res, 403, { error: 'missing x-host-services header' })
      lan.scan()
      return sendJSON(res, 202, { ok: true, scanning: true })
    }
    if (url.startsWith('/api/')) return sendJSON(res, 404, { error: 'not found' })

    const asset = ASSETS[url]
    if (!asset || req.method !== 'GET') {
      res.writeHead(404, { 'content-type': 'text/plain' })
      return res.end('not found')
    }
    let body
    try {
      body = fs.readFileSync(path.join(STATIC_DIR, asset[0]))
    } catch {
      res.writeHead(500, { 'content-type': 'text/plain' })
      return res.end('asset missing: ' + asset[0])
    }
    res.writeHead(200, { 'content-type': asset[1], 'content-length': body.length, 'cache-control': 'no-cache' })
    res.end(body)
  })
}