MCPcopy Create free account
hub / github.com/QodeXcli/QodeX / createLiveServer

Function createLiveServer

src/artifacts/live-server.ts:100–235  ·  view source on GitHub ↗
(opts: CreateLiveServerOptions)

Source from the content-addressed store, hash-verified

98}
99
100export async function createLiveServer(opts: CreateLiveServerOptions): Promise<LiveServerHandle> {
101 const { cwd, id } = opts;
102 const channelPath = opts.channelPath ?? LIVE_CHANNEL_PATH;
103 const debounceMs = opts.debounceMs ?? 120;
104 const host = opts.host ?? '127.0.0.1';
105 const artifactDir = path.join(artifactsRoot(cwd), id);
106
107 const clients = new Set<ServerResponse>();
108 let lastHash: string | null = null;
109 let closed = false;
110
111 const pushReload = (hash: string) => {
112 const frame = `event: reload\ndata: ${hash}\n\n`;
113 for (const res of clients) {
114 try { res.write(frame); } catch { /* dropped client; pruned on its 'close' */ }
115 }
116 };
117
118 const flush = () => {
119 void renderCurrent(cwd, id, channelPath).then(html => {
120 const h = hashHtml(html);
121 if (!shouldPush(lastHash, h)) return; // dedupe rename double-fire / no-op rewrites
122 lastHash = h;
123 pushReload(h);
124 }).catch(err => logger.warn('Live artifact flush failed', { id, err: err?.message ?? String(err) }));
125 };
126 const debouncedFlush = makeDebouncer(flush, debounceMs);
127
128 // ── HTTP server ──────────────────────────────────────────────────────────
129 const token = opts.token;
130 const server: Server = createServer((req, res) => {
131 const url = req.url ?? '/';
132 // Token gate: a network-/tunnel-exposed server rejects anyone without the link's token.
133 const auth = checkLiveAuth(token, url, req.headers.cookie);
134 if (!auth.ok) {
135 res.writeHead(403, { 'Content-Type': 'text/plain; charset=utf-8' });
136 res.end('403 — invalid or missing access token. Open the full private link (it ends with ?k=…).');
137 return;
138 }
139 const cookieHeader: Record<string, string> = auth.setCookie
140 ? { 'Set-Cookie': `qx_live=${token}; Path=/; SameSite=Lax; HttpOnly; Max-Age=86400` }
141 : {};
142 if (url === channelPath || url.startsWith(channelPath + '?')) {
143 // SSE channel: keep open, push reload events.
144 res.writeHead(200, {
145 'Content-Type': 'text/event-stream',
146 'Cache-Control': 'no-cache, no-transform',
147 'Connection': 'keep-alive',
148 ...cookieHeader,
149 });
150 res.flushHeaders?.();
151 res.write('retry: 1000\n\n');
152 res.write(': connected\n\n');
153 clients.add(res);
154 const heartbeat = setInterval(() => {
155 try { res.write(': ping\n\n'); } catch { /* ignore */ }
156 }, 25_000);
157 const cleanup = () => { clearInterval(heartbeat); clients.delete(res); };

Callers 1

startLiveFunction · 0.85

Calls 9

artifactsRootFunction · 0.85
makeDebouncerFunction · 0.85
checkLiveAuthFunction · 0.85
renderCurrentFunction · 0.85
listenOnFunction · 0.85
hashHtmlFunction · 0.85
addMethod · 0.80
warnMethod · 0.80
writeMethod · 0.45

Tested by

no test coverage detected