MCPcopy
hub / github.com/cpaczek/skylight / constructor

Method constructor

tracker/src/hub.ts:28–105  ·  view source on GitHub ↗
(
    private loop: ControlLoop,
    private upstream: Upstream,
    private recorder: Recorder,
    private video: VideoStream,
    private videoRec: VideoRecorder,
  )

Source from the content-addressed store, hash-verified

26 private stateTimer: ReturnType<typeof setInterval> | null = null;
27
28 constructor(
29 private loop: ControlLoop,
30 private upstream: Upstream,
31 private recorder: Recorder,
32 private video: VideoStream,
33 private videoRec: VideoRecorder,
34 ) {
35 const app = express();
36 // The debug UI is served from :3000 but talks to this tracker on :3001 of
37 // the SAME host, so its fetch()es are cross-origin. Allow cross-port reads
38 // from the same hostname (covers prod :3000, dev :5173, and LAN-IP access)
39 // while still rejecting foreign origins; answer the CORS preflight.
40 app.use((req, res, next) => {
41 const origin = req.headers.origin;
42 if (origin) {
43 let sameHost = false;
44 try {
45 sameHost = new URL(origin).hostname === req.hostname;
46 } catch {
47 /* malformed Origin — treat as not allowed */
48 }
49 if (sameHost) {
50 res.setHeader("Access-Control-Allow-Origin", origin);
51 res.setHeader("Vary", "Origin");
52 res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
53 res.setHeader("Access-Control-Allow-Headers", "content-type");
54 }
55 }
56 if (req.method === "OPTIONS") return res.sendStatus(204);
57 next();
58 });
59 app.use(express.json());
60
61 app.get("/api/tracker/health", (_req, res) => res.json({ ok: true }));
62 app.get("/api/tracker/state", (_req, res) => res.json(this.loop.getState()));
63 app.get("/api/tracker/config", (_req, res) =>
64 res.json(this.upstream.getConfig().tracker),
65 );
66 app.get("/video", (_req, res) => this.video.addClient(res));
67 app.get("/frame.jpg", (_req, res) => {
68 const frame = this.video.latestFrame();
69 if (!frame) return res.status(503).json({ error: "no frame yet" });
70 res.type("image/jpeg").send(frame);
71 });
72 // The frame as the detector sees it: red = masked clutter, green = blob.
73 app.get("/vision-debug.jpg", (_req, res) => {
74 const frame = this.video.latestFrame();
75 if (!frame) return res.status(503).json({ error: "no frame yet" });
76 renderDebug(frame)
77 .then((img) => res.type("image/jpeg").send(img))
78 .catch((err) => res.status(500).json({ error: String(err) }));
79 });
80
81 // --- full-quality clip recording ---
82 app.post("/api/record/video", (req, res) => {
83 if (req.body?.on) res.json(this.videoRec.start());
84 else {
85 this.videoRec.stop();

Callers

nothing calls this directly

Calls 14

onConnectMethod · 0.95
renderDebugFunction · 0.85
getStateMethod · 0.80
addClientMethod · 0.80
latestFrameMethod · 0.80
listMethod · 0.80
resolveMethod · 0.80
getConfigMethod · 0.65
startMethod · 0.65
stopMethod · 0.65
getMethod · 0.45
statusMethod · 0.45

Tested by

no test coverage detected