@bananacool467/authtics-host 0.2.5 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,26 +1,192 @@
1
1
  #!/usr/bin/env node
2
2
  import { createServer } from "node:http";
3
3
  import { createJiti } from "jiti";
4
+ import { createHash } from "node:crypto";
4
5
  import path from "node:path";
5
6
  import fs from "node:fs";
6
7
  import url from "node:url";
7
- import { renderPage } from "./renderEngine.js";
8
+ import { renderPage, handleSrtwsConnection, deleteSrtwsServer, handleHTML } from "./renderEngine.js";
9
+ import { setBuildStyle, tempFileForTsx } from "./rendererTempFiles.js";
10
+ import { initializeSuperBuildOutput, refreshSuperRouteManifest } from "./superBuildOutput.js";
8
11
  let config_ = {};
9
12
  let uptime = 0;
10
13
  let totalRequests = 0;
11
14
  let users = 0;
12
15
  let usersPaused = false;
16
+ let srtws;
13
17
  const jiti = createJiti(import.meta.url);
14
18
  const routes = new Map();
15
19
  const __filename = url.fileURLToPath(import.meta.url);
16
20
  const __dirname = path.dirname(__filename);
17
21
  const hmrClients = new Set();
22
+ const DAT_GUESS_WINDOW_MS = 30 * 60 * 1000;
23
+ const DAT_DISTINCT_GUESS_LIMIT = 5;
24
+ const DAT_IP_BLOCK_MS = 24 * 60 * 60 * 1000;
25
+ const ACTION_RATE_WINDOW_MS = 60 * 1000;
26
+ const ACTION_RATE_RULES = {
27
+ "/__authtics_host_apis__/set-users-paused": {
28
+ maxRequests: 5,
29
+ windowMs: ACTION_RATE_WINDOW_MS,
30
+ bucket: "pause",
31
+ },
32
+ "/__authtics_host_apis__/restart": {
33
+ maxRequests: 1,
34
+ windowMs: ACTION_RATE_WINDOW_MS,
35
+ bucket: "lifecycle",
36
+ },
37
+ "/__authtics_host_apis__/shutdown": {
38
+ maxRequests: 1,
39
+ windowMs: ACTION_RATE_WINDOW_MS,
40
+ bucket: "lifecycle",
41
+ },
42
+ };
43
+ const failedDatGuesses = new Map();
44
+ const blockedDatIps = new Map();
45
+ const actionRequestTimes = new Map();
46
+ let datLockoutFile = "";
47
+ function fingerprint(value) {
48
+ return createHash("sha256").update(value).digest("hex");
49
+ }
50
+ function clientIpForRateLimit(req) {
51
+ return (req.ip || req.socket.remoteAddress || "unknown").trim();
52
+ }
53
+ function persistDatLockouts() {
54
+ if (!datLockoutFile)
55
+ return;
56
+ try {
57
+ fs.mkdirSync(path.dirname(datLockoutFile), { recursive: true });
58
+ const temporaryPath = `${datLockoutFile}.${process.pid}.tmp`;
59
+ fs.writeFileSync(temporaryPath, JSON.stringify(Object.fromEntries(blockedDatIps)), { encoding: "utf8", mode: 0o600 });
60
+ try {
61
+ fs.chmodSync(temporaryPath, 0o600);
62
+ }
63
+ catch {
64
+ }
65
+ fs.renameSync(temporaryPath, datLockoutFile);
66
+ }
67
+ catch (error) {
68
+ console.error("[HostJS] Could not persist DAT lockouts:", error);
69
+ }
70
+ }
71
+ function loadDatLockouts() {
72
+ if (!datLockoutFile)
73
+ return;
74
+ try {
75
+ const parsed = JSON.parse(fs.readFileSync(datLockoutFile, "utf8"));
76
+ const now = Date.now();
77
+ for (const [ipHash, until] of Object.entries(parsed)) {
78
+ if (typeof until === "number" && until > now) {
79
+ blockedDatIps.set(ipHash, until);
80
+ }
81
+ }
82
+ }
83
+ catch (error) {
84
+ if (error.code !== "ENOENT") {
85
+ console.warn("[HostJS] Could not read DAT lockouts; starting with an empty lockout list.");
86
+ }
87
+ }
88
+ }
89
+ function sendDatGuardResponse(res, statusCode, code, message, retryAfterSeconds) {
90
+ const headers = {
91
+ "Content-Type": "application/json",
92
+ "Cache-Control": "no-store",
93
+ };
94
+ if (retryAfterSeconds !== undefined) {
95
+ headers["Retry-After"] = String(retryAfterSeconds);
96
+ }
97
+ res.writeHead(statusCode, headers);
98
+ res.end(JSON.stringify({ error: code, message, retryAfter: retryAfterSeconds }));
99
+ }
100
+ function recordInvalidDatGuess(ipHash, suppliedDat, now) {
101
+ let guesses = failedDatGuesses.get(ipHash);
102
+ if (!guesses) {
103
+ guesses = new Map();
104
+ failedDatGuesses.set(ipHash, guesses);
105
+ }
106
+ for (const [candidateHash, timestamp] of guesses) {
107
+ if (now - timestamp >= DAT_GUESS_WINDOW_MS)
108
+ guesses.delete(candidateHash);
109
+ }
110
+ guesses.set(fingerprint(suppliedDat), now);
111
+ if (guesses.size >= DAT_DISTINCT_GUESS_LIMIT) {
112
+ blockedDatIps.set(ipHash, now + DAT_IP_BLOCK_MS);
113
+ failedDatGuesses.delete(ipHash);
114
+ persistDatLockouts();
115
+ return true;
116
+ }
117
+ return false;
118
+ }
119
+ function checkActionRateLimit(ipHash, pathname, now) {
120
+ const rule = ACTION_RATE_RULES[pathname];
121
+ if (!rule)
122
+ return undefined;
123
+ const key = `${ipHash}:${rule.bucket}`;
124
+ const recent = (actionRequestTimes.get(key) ?? [])
125
+ .filter(timestamp => now - timestamp < rule.windowMs);
126
+ if (recent.length >= rule.maxRequests) {
127
+ actionRequestTimes.set(key, recent);
128
+ return Math.max(1, Math.ceil((recent[0] + rule.windowMs - now) / 1000));
129
+ }
130
+ recent.push(now);
131
+ actionRequestTimes.set(key, recent);
132
+ return undefined;
133
+ }
134
+ function guardDatApiRequest(req, res, pathname) {
135
+ const now = Date.now();
136
+ const ipHash = fingerprint(clientIpForRateLimit(req));
137
+ const blockedUntil = blockedDatIps.get(ipHash);
138
+ if (blockedUntil !== undefined) {
139
+ if (blockedUntil > now) {
140
+ sendDatGuardResponse(res, 429, "DAT_IP_BLOCKED", "DAT-protected endpoints are temporarily unavailable from this IP.", Math.ceil((blockedUntil - now) / 1000));
141
+ return false;
142
+ }
143
+ blockedDatIps.delete(ipHash);
144
+ persistDatLockouts();
145
+ }
146
+ const configuredDats = typeof config_.dat === "string"
147
+ ? [config_.dat]
148
+ : Array.isArray(config_.dat)
149
+ ? config_.dat.filter((value) => typeof value === "string")
150
+ : [];
151
+ const datRestrictionEnabled = config_.dat !== undefined && config_.dat !== null;
152
+ const datMatches = !datRestrictionEnabled ||
153
+ configuredDats.includes(req.dat ?? "");
154
+ if (datRestrictionEnabled && !datMatches) {
155
+ const justBlocked = recordInvalidDatGuess(ipHash, req.dat ?? "<missing>", now);
156
+ if (justBlocked) {
157
+ sendDatGuardResponse(res, 429, "DAT_IP_BLOCKED", "Too many distinct invalid DAT values. DAT-protected endpoints are blocked for 24 hours.", Math.ceil(DAT_IP_BLOCK_MS / 1000));
158
+ }
159
+ else {
160
+ sendDatGuardResponse(res, 401, "INVALID_DAT", "DAT authorization failed.");
161
+ }
162
+ return false;
163
+ }
164
+ if (datRestrictionEnabled) {
165
+ failedDatGuesses.delete(ipHash);
166
+ }
167
+ const retryAfter = checkActionRateLimit(ipHash, pathname, now);
168
+ if (retryAfter !== undefined) {
169
+ sendDatGuardResponse(res, 429, "ACTION_RATE_LIMITED", "This action is temporarily rate-limited. Try again later.", retryAfter);
170
+ return false;
171
+ }
172
+ return true;
173
+ }
18
174
  function safeWatch(target, onChange) {
19
175
  const shouldReact = (filename) => {
20
176
  if (!filename)
21
177
  return false;
22
178
  if (filename.includes('.authtics_host'))
23
179
  return false;
180
+ if (filename.includes('.hostjs'))
181
+ return false;
182
+ if (filename.includes('.upm'))
183
+ return false;
184
+ if (filename.includes('.tsbuildinfo'))
185
+ return false;
186
+ if (filename.includes('.local'))
187
+ return false;
188
+ if (filename.includes('.cache'))
189
+ return false;
24
190
  if (filename.includes('node_modules'))
25
191
  return false;
26
192
  return /\.(tsx?|jsx?|html|css)$/i.test(filename);
@@ -67,20 +233,89 @@ function notifyHmrClients() {
67
233
  hmrDebounceTimer = setTimeout(() => {
68
234
  hmrDebounceTimer = null;
69
235
  for (const client of hmrClients) {
236
+ if (client.destroyed || client.writableEnded) {
237
+ hmrClients.delete(client);
238
+ continue;
239
+ }
70
240
  try {
71
241
  client.write('data: reload\n\n');
72
242
  }
73
243
  catch {
74
244
  hmrClients.delete(client);
245
+ client.destroy();
75
246
  }
76
247
  }
77
248
  }, 120);
78
249
  }
79
250
  const MIME_TYPES = {
80
251
  '.html': 'text/html',
252
+ '.htm': 'text/html',
81
253
  '.css': 'text/css',
82
254
  '.js': 'application/javascript',
255
+ '.mjs': 'application/javascript',
256
+ '.jsx': 'text/jsx',
257
+ '.ts': 'text/typescript',
258
+ '.tsx': 'text/tsx',
83
259
  '.json': 'application/json',
260
+ '.jsonld': 'application/ld+json',
261
+ '.map': 'application/json',
262
+ '.c': 'text/x-csrc',
263
+ '.cpp': 'text/x-c++src',
264
+ '.h': 'text/x-chdr',
265
+ '.hpp': 'text/x-c++hdr',
266
+ '.cs': 'text/x-csharp',
267
+ '.go': 'text/x-gosrc',
268
+ '.py': 'text/x-python',
269
+ '.rb': 'text/x-ruby',
270
+ '.php': 'application/x-httpd-php',
271
+ '.java': 'text/x-java-source',
272
+ '.kt': 'text/x-kotlin',
273
+ '.swift': 'text/x-swift',
274
+ '.rs': 'text/rust',
275
+ '.sh': 'application/x-sh',
276
+ '.bat': 'application/x-msdos-program',
277
+ '.ps1': 'application/x-powershell',
278
+ '.pl': 'text/x-perl',
279
+ '.r': 'text/x-rsrc',
280
+ '.sql': 'text/x-sql',
281
+ '.xml': 'application/xml',
282
+ '.yaml': 'text/yaml',
283
+ '.yml': 'text/yaml',
284
+ '.toml': 'text/toml',
285
+ '.ini': 'text/plain',
286
+ '.cfg': 'text/plain',
287
+ '.conf': 'text/plain',
288
+ '.md': 'text/markdown',
289
+ '.txt': 'text/plain',
290
+ '.csv': 'text/csv',
291
+ '.tsv': 'text/tab-separated-values',
292
+ '.pdf': 'application/pdf',
293
+ '.wasm': 'application/wasm',
294
+ '.elf': 'application/x-elf',
295
+ '.exe': 'application/x-msdownload',
296
+ '.dll': 'application/x-msdownload',
297
+ '.o': 'application/x-object',
298
+ '.a': 'application/x-archive',
299
+ '.so': 'application/x-sharedlib',
300
+ '.dylib': 'application/x-apple-binary',
301
+ '.msi': 'application/x-msi',
302
+ '.apk': 'application/vnd.android.package-archive',
303
+ '.bin': 'application/octet-stream',
304
+ '.iso': 'application/x-iso9660-image',
305
+ '.img': 'application/octet-stream',
306
+ '.dmg': 'application/x-apple-diskimage',
307
+ '.vmdk': 'application/x-virtualbox-vmdk',
308
+ '.vhd': 'application/x-vhd',
309
+ '.qcow2': 'application/x-qcow2',
310
+ '.zip': 'application/zip',
311
+ '.tar': 'application/x-tar',
312
+ '.gz': 'application/gzip',
313
+ '.tgz': 'application/gzip',
314
+ '.bz2': 'application/x-bzip2',
315
+ '.xz': 'application/x-xz',
316
+ '.rar': 'application/vnd.rar',
317
+ '.7z': 'application/x-7z-compressed',
318
+ '.cab': 'application/vnd.ms-cab-compressed',
84
319
  '.png': 'image/png',
85
320
  '.jpg': 'image/jpeg',
86
321
  '.jpeg': 'image/jpeg',
@@ -88,13 +323,49 @@ const MIME_TYPES = {
88
323
  '.svg': 'image/svg+xml',
89
324
  '.ico': 'image/x-icon',
90
325
  '.webp': 'image/webp',
326
+ '.avif': 'image/avif',
327
+ '.bmp': 'image/bmp',
328
+ '.tiff': 'image/tiff',
329
+ '.tif': 'image/tiff',
330
+ '.psd': 'image/vnd.adobe.photoshop',
331
+ '.ai': 'application/postscript',
332
+ '.mp4': 'video/mp4',
333
+ '.mkv': 'video/x-matroska',
334
+ '.avi': 'video/x-msvideo',
335
+ '.mov': 'video/quicktime',
336
+ '.wmv': 'video/x-ms-wmv',
337
+ '.webm': 'video/webm',
338
+ '.flv': 'video/x-flv',
339
+ '.mp3': 'audio/mpeg',
340
+ '.wav': 'audio/wav',
341
+ '.ogg': 'audio/ogg',
342
+ '.oga': 'audio/ogg',
343
+ '.ogv': 'video/ogg',
344
+ '.flac': 'audio/flac',
345
+ '.aac': 'audio/aac',
346
+ '.m4a': 'audio/x-m4a',
91
347
  '.woff': 'font/woff',
92
348
  '.woff2': 'font/woff2',
93
349
  '.ttf': 'font/ttf',
94
- '.mp4': 'video/mp4',
95
- '.mp3': 'audio/mpeg',
96
- '.txt': 'text/plain',
97
- '.pdf': 'application/pdf',
350
+ '.otf': 'font/otf',
351
+ '.eot': 'application/vnd.ms-fontobject',
352
+ '.sqlite': 'application/vnd.sqlite3',
353
+ '.sqlite3': 'application/vnd.sqlite3',
354
+ '.db': 'application/octet-stream',
355
+ '.mdb': 'application/x-msaccess',
356
+ '.obj': 'model/obj',
357
+ '.stl': 'model/stl',
358
+ '.gltf': 'model/gltf+json',
359
+ '.glb': 'model/gltf-binary',
360
+ '.fbx': 'application/octet-stream',
361
+ '.dae': 'model/vnd.collada+xml',
362
+ '.3ds': 'image/x-3ds',
363
+ '.doc': 'application/msword',
364
+ '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
365
+ '.xls': 'application/vnd.ms-excel',
366
+ '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
367
+ '.ppt': 'application/vnd.ms-powerpoint',
368
+ '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
98
369
  };
99
370
  const getCpu = () => {
100
371
  return new Promise((resolve) => {
@@ -258,7 +529,7 @@ export default class App {
258
529
  stdio: 'inherit'
259
530
  });
260
531
  child.unref();
261
- this.server.close(() => {
532
+ this.server?.close(() => {
262
533
  process.kill(process.pid, 'SIGTERM');
263
534
  });
264
535
  }
@@ -272,12 +543,12 @@ export default class App {
272
543
  if (datMatched) {
273
544
  res.writeHead(200, { "Content-Type": "application/json" });
274
545
  res.end(JSON.stringify({ message: "Shutting down..." }));
275
- this.server.close(() => {
546
+ this.server?.close(() => {
276
547
  process.kill(process.pid, 'SIGTERM');
277
548
  });
278
549
  }
279
550
  });
280
- this.post("/__authtics_host_apis__/get-is-ssr", async (req, res) => {
551
+ this.post("/__authtics_host_apis__/get-render-type", async (req, res) => {
281
552
  let datMatched = false;
282
553
  if (config_.dat && config_.dat == req.dat)
283
554
  datMatched = true;
@@ -286,7 +557,7 @@ export default class App {
286
557
  if (datMatched) {
287
558
  res.writeHead(200, { "Content-Type": "application/json" });
288
559
  res.end(JSON.stringify({
289
- isSsr: config_.ssr
560
+ renderType: config_.renderType
290
561
  }));
291
562
  }
292
563
  });
@@ -388,15 +659,15 @@ export default class App {
388
659
  this.server = await start();
389
660
  }
390
661
  catch (error) {
391
- console.error("Server failed to start:", error);
662
+ console.error("[HostJS] Server failed to start: ", error);
392
663
  }
393
664
  }
394
- get(routePath, handler) {
395
- routes.set(`GET:${routePath}`, handler);
396
- }
397
665
  registerApp(route) {
398
666
  routes.set(`APP_TSX`, route);
399
667
  }
668
+ get(routePath, handler) {
669
+ routes.set(`GET:${routePath}`, handler);
670
+ }
400
671
  post(routePath, handler) {
401
672
  routes.set(`POST:${routePath}`, handler);
402
673
  }
@@ -406,6 +677,15 @@ export default class App {
406
677
  delete(routePath, handler) {
407
678
  routes.set(`DELETE:${routePath}`, handler);
408
679
  }
680
+ use(handler) {
681
+ const randomMultiplier = BigInt(Math.floor(Math.random() * 1000000000));
682
+ let newId = randomMultiplier * 1000000000000000000000000000000n;
683
+ routes.set(`USE:${newId}`, handler);
684
+ }
685
+ on(method, handler) {
686
+ if (this.server)
687
+ this.server.on(method, handler);
688
+ }
409
689
  remove(method, routePath) {
410
690
  if (routes.has(`${method}:${routePath}`))
411
691
  routes.delete(`${method}:${routePath}`);
@@ -420,7 +700,14 @@ async function start() {
420
700
  }
421
701
  catch {
422
702
  }
703
+ const requestedBuildStyle = config.buildStyle ?? "default";
704
+ if (requestedBuildStyle !== "default" && requestedBuildStyle !== "super") {
705
+ throw new Error('[HostJS] Invalid buildStyle. Expected "default" or "super".');
706
+ }
707
+ const buildStyle = requestedBuildStyle;
708
+ setBuildStyle(buildStyle, process.cwd());
423
709
  const PORT = config.port ?? 3000;
710
+ const renderType = config.renderType ?? 'ssr';
424
711
  const showPanel = config.devMode !== false;
425
712
  const autoPaging = config.autoPage !== false;
426
713
  const compiler = 'esbuild';
@@ -428,6 +715,9 @@ async function start() {
428
715
  const pagesDir = config.autoPage === true ? path.join(process.cwd(), config.autoPageDir || "") : undefined;
429
716
  const publicDir = config.autoFile === true ? path.join(process.cwd(), config.autoFileDir || "") : undefined;
430
717
  const apiDir = pagesDir ? path.join(pagesDir, "api") : undefined;
718
+ if (buildStyle === "super") {
719
+ initializeSuperBuildOutput(process.cwd(), config, pagesDir, publicDir);
720
+ }
431
721
  const server = createServer(async (req, res) => {
432
722
  const { url = "/", method } = req;
433
723
  const actuallyShowPanel = showPanel;
@@ -539,8 +829,17 @@ async function start() {
539
829
  res.writeHead(302, { Location: req.url + '/' });
540
830
  res.end();
541
831
  };
542
- res.setCookie = function (name, value) {
543
- res.setHeader("Set-Cookie", `${name}=${value}; Path=/`);
832
+ res.setCookie = function (name, value, sameSite = "Lax", HttpOnly = true, Secure = true, MaxAge = null) {
833
+ const cookieParts = [`${name}=${value}`, "Path=/"];
834
+ if (MaxAge !== null)
835
+ cookieParts.push(`Max-Age=${MaxAge}`);
836
+ if (HttpOnly)
837
+ cookieParts.push("HttpOnly");
838
+ if (Secure)
839
+ cookieParts.push("Secure");
840
+ if (sameSite)
841
+ cookieParts.push(`SameSite=${sameSite}`);
842
+ res.setHeader("Set-Cookie", cookieParts.join("; "));
544
843
  };
545
844
  res.rmvCookie = function (name) {
546
845
  res.setHeader("Set-Cookie", `${name}=; Path=/; Max-Age=0`);
@@ -550,9 +849,28 @@ async function start() {
550
849
  pathname = safePath;
551
850
  if (pathname !== "/" && pathname.endsWith("/"))
552
851
  pathname = pathname.slice(0, -1);
553
- if (!pathname.includes('/__authtics') && !pathname.includes('/hmr')) {
852
+ if (!pathname.includes('/__authtics') && !pathname.includes('/hmr') && !pathname.includes('/srtws')) {
554
853
  totalRequests++;
555
854
  }
855
+ const useRoutes = Array.from(routes.entries()).filter(([key]) => key.startsWith("USE:"));
856
+ if (useRoutes.length > 0 && !pathname.includes('/__authtics') && !pathname.includes('/hmr') && !pathname.includes('/srtws')) {
857
+ let i = 0;
858
+ const next = () => {
859
+ i++;
860
+ if (i >= useRoutes.length)
861
+ return;
862
+ const [key, handler] = useRoutes[i];
863
+ if (typeof handler === 'function') {
864
+ const id = BigInt(key.split(":")[1]);
865
+ handler(req, res, id, next);
866
+ }
867
+ };
868
+ const [key, handler] = useRoutes[0];
869
+ if (typeof handler === 'function') {
870
+ const id = BigInt(key.split(":")[1]);
871
+ handler(req, res, id, next);
872
+ }
873
+ }
556
874
  const routeKey = `${method}:${pathname}`;
557
875
  if (routes.has(routeKey)) {
558
876
  if (Array.isArray(config_.dat) && !config_.dat.includes(req.dat) && usersPaused && !routeKey.endsWith('.js')) {
@@ -561,17 +879,17 @@ async function start() {
561
879
  if (fs.existsSync(pausedTsxFile || "")) {
562
880
  try {
563
881
  let html = await renderPage(pausedTsxFile || "", url, actuallyShowPanel, compiler, hotReload, autoPaging, config.autoPageDir, (routes.get("APP_TSX") || ""), (f, p) => {
564
- routes.set(`GET:${p}`, (req, res) => {
565
- res.setHeader("Content-Type", "application/javascript");
882
+ routes.set(`GET:${p}`, (_, res) => {
883
+ res.setHeader("Content-Type", p.toLowerCase().endsWith(".map") ? "application/json" : "application/javascript");
566
884
  res.end(f);
567
885
  });
568
- }, (p) => { routes.delete(`GET:${p}`); }, req.dat, config_.dat);
886
+ }, (p) => { routes.delete(`GET:${p}`); }, req.dat, config_.dat, config_.renderType, config_.patchForSsr);
569
887
  html = html.replace("<script>", "<script>\nwindow.__paused__ = true;");
570
888
  res.setHeader("Content-Type", "text/html");
571
889
  return res.end(html);
572
890
  }
573
891
  catch (e) {
574
- console.error("[authtics] renderPage error:", e.message);
892
+ console.error("[HostJS] renderPage error:", e.message);
575
893
  res.writeHead(500);
576
894
  return res.end(`<html><body><pre>500 | Render error:\n${e.message}</pre></body></html>`);
577
895
  }
@@ -584,17 +902,17 @@ async function start() {
584
902
  if (fs.existsSync(appPath)) {
585
903
  try {
586
904
  let html = await renderPage(appPath, url, actuallyShowPanel, compiler, hotReload, autoPaging, config.autoPageDir, (routes.get("APP_TSX") || ""), (f, p) => {
587
- routes.set(`GET:${p}`, (req, res) => {
588
- res.setHeader("Content-Type", "application/javascript");
905
+ routes.set(`GET:${p}`, (_, res) => {
906
+ res.setHeader("Content-Type", p.toLowerCase().endsWith(".map") ? "application/json" : "application/javascript");
589
907
  res.end(f);
590
908
  });
591
- }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat);
909
+ }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat, config_.renderType);
592
910
  html = html.replace("<script>", "<script>\nwindow.__paused__ = true;");
593
911
  res.setHeader("Content-Type", "text/html");
594
912
  return res.end(html);
595
913
  }
596
914
  catch (e) {
597
- console.error("[authtics] renderPage error:", e.message);
915
+ console.error("[HostJS] renderPage error:", e.message);
598
916
  res.writeHead(500);
599
917
  return res.end(`<html><body><pre>500 | Render error:\n${e.message}</pre></body></html>`);
600
918
  }
@@ -614,17 +932,17 @@ async function start() {
614
932
  if (fs.existsSync(pausedTsxFile || "")) {
615
933
  try {
616
934
  let html = await renderPage(pausedTsxFile || "", url, actuallyShowPanel, compiler, hotReload, autoPaging, config.autoPageDir, (routes.get("APP_TSX") || ""), (f, p) => {
617
- routes.set(`GET:${p}`, (req, res) => {
618
- res.setHeader("Content-Type", "application/javascript");
935
+ routes.set(`GET:${p}`, (_, res) => {
936
+ res.setHeader("Content-Type", p.toLowerCase().endsWith(".map") ? "application/json" : "application/javascript");
619
937
  res.end(f);
620
938
  });
621
- }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat);
939
+ }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat, config_.renderType, config_.patchForSsr);
622
940
  html = html.replace("<script>", "<script>\nwindow.__paused__ = true;");
623
941
  res.setHeader("Content-Type", "text/html");
624
942
  return res.end(html);
625
943
  }
626
944
  catch (e) {
627
- console.error("[authtics] renderPage error:", e.message);
945
+ console.error("[HostJS] renderPage error:", e.message);
628
946
  res.writeHead(500);
629
947
  return res.end(`<html><body><pre>500 | Render error:\n${e.message}</pre></body></html>`);
630
948
  }
@@ -637,17 +955,17 @@ async function start() {
637
955
  if (fs.existsSync(appPath)) {
638
956
  try {
639
957
  let html = await renderPage(appPath, url, actuallyShowPanel, compiler, hotReload, autoPaging, config.autoPageDir, (routes.get("APP_TSX") || ""), (f, p) => {
640
- routes.set(`GET:${p}`, (req, res) => {
641
- res.setHeader("Content-Type", "application/javascript");
958
+ routes.set(`GET:${p}`, (_, res) => {
959
+ res.setHeader("Content-Type", p.toLowerCase().endsWith(".map") ? "application/json" : "application/javascript");
642
960
  res.end(f);
643
961
  });
644
- }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat);
962
+ }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat, config_.renderType);
645
963
  html = html.replace("<script>", "<script>\nwindow.__paused__ = true;");
646
964
  res.setHeader("Content-Type", "text/html");
647
965
  return res.end(html);
648
966
  }
649
967
  catch (e) {
650
- console.error("[authtics] renderPage error:", e.message);
968
+ console.error("[HostJS] renderPage error:", e.message);
651
969
  res.writeHead(500);
652
970
  return res.end(`<html><body><pre>500 | Render error:\n${e.message}</pre></body></html>`);
653
971
  }
@@ -662,6 +980,17 @@ async function start() {
662
980
  }));
663
981
  }
664
982
  const handler = routes.get(routeKey);
983
+ if (typeof handler === "function" && routeKey.toLowerCase().endsWith(".map")) {
984
+ const configuredDAT = config_.dat;
985
+ const hasMatchingDAT = configuredDAT === undefined ||
986
+ (Array.isArray(configuredDAT)
987
+ ? configuredDAT.includes(req.dat ?? "")
988
+ : configuredDAT === req.dat);
989
+ if (!hasMatchingDAT) {
990
+ res.writeHead(404);
991
+ return res.end();
992
+ }
993
+ }
665
994
  if (typeof handler === 'string') {
666
995
  return;
667
996
  }
@@ -672,14 +1001,24 @@ async function start() {
672
1001
  if (pathname === '/hmr') {
673
1002
  res.writeHead(200, {
674
1003
  'Content-Type': 'text/event-stream',
675
- 'Cache-Control': 'no-cache',
1004
+ 'Cache-Control': 'no-cache, no-transform',
676
1005
  'Connection': 'keep-alive',
677
1006
  'Access-Control-Allow-Origin': '*',
678
1007
  });
679
1008
  res.write('data: connected\n\n');
680
1009
  hmrClients.add(res);
681
1010
  users++;
682
- req.on('close', () => { hmrClients.delete(res); users--; });
1011
+ const cleanup = () => {
1012
+ hmrClients.delete(res);
1013
+ users = Math.max(0, users - 1);
1014
+ };
1015
+ req.on('close', cleanup);
1016
+ req.on('aborted', cleanup);
1017
+ res.on('close', cleanup);
1018
+ res.on('error', cleanup);
1019
+ return;
1020
+ }
1021
+ if (pathname === '/srtws') {
683
1022
  return;
684
1023
  }
685
1024
  if (Array.isArray(config_.dat) && !config_.dat.includes(req.dat) && usersPaused) {
@@ -688,17 +1027,17 @@ async function start() {
688
1027
  if (fs.existsSync(pausedTsxFile || "")) {
689
1028
  try {
690
1029
  let html = await renderPage(pausedTsxFile || "", url, actuallyShowPanel, compiler, hotReload, autoPaging, config.autoPageDir, (routes.get("APP_TSX") || ""), (f, p) => {
691
- routes.set(`GET:${p}`, (req, res) => {
692
- res.setHeader("Content-Type", "application/javascript");
1030
+ routes.set(`GET:${p}`, (_, res) => {
1031
+ res.setHeader("Content-Type", p.toLowerCase().endsWith(".map") ? "application/json" : "application/javascript");
693
1032
  res.end(f);
694
1033
  });
695
- }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat);
1034
+ }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat, config_.renderType, config_.patchForSsr);
696
1035
  html = html.replace("<script>", "<script>\nwindow.__paused__ = true;");
697
1036
  res.setHeader("Content-Type", "text/html");
698
1037
  return res.end(html);
699
1038
  }
700
1039
  catch (e) {
701
- console.error("[authtics] renderPage error:", e.message);
1040
+ console.error("[HostJS] renderPage error:", e.message);
702
1041
  res.writeHead(500);
703
1042
  return res.end(`<html><body><pre>500 | Render error:\n${e.message}</pre></body></html>`);
704
1043
  }
@@ -711,17 +1050,17 @@ async function start() {
711
1050
  if (fs.existsSync(appPath)) {
712
1051
  try {
713
1052
  let html = await renderPage(appPath, url, actuallyShowPanel, compiler, hotReload, autoPaging, config.autoPageDir, (routes.get("APP_TSX") || ""), (f, p) => {
714
- routes.set(`GET:${p}`, (req, res) => {
715
- res.setHeader("Content-Type", "application/javascript");
1053
+ routes.set(`GET:${p}`, (_, res) => {
1054
+ res.setHeader("Content-Type", p.toLowerCase().endsWith(".map") ? "application/json" : "application/javascript");
716
1055
  res.end(f);
717
1056
  });
718
- }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat);
1057
+ }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat, config_.renderType, config_.patchForSsr);
719
1058
  html = html.replace("<script>", "<script>\nwindow.__paused__ = true;");
720
1059
  res.setHeader("Content-Type", "text/html");
721
1060
  return res.end(html);
722
1061
  }
723
1062
  catch (e) {
724
- console.error("[authtics] renderPage error:", e.message);
1063
+ console.error("[HostJS] renderPage error:", e.message);
725
1064
  res.writeHead(500);
726
1065
  return res.end(`<html><body><pre>500 | Render error:\n${e.message}</pre></body></html>`);
727
1066
  }
@@ -741,17 +1080,17 @@ async function start() {
741
1080
  if (fs.existsSync(pausedTsxFile || "")) {
742
1081
  try {
743
1082
  let html = await renderPage(pausedTsxFile || "", url, actuallyShowPanel, compiler, hotReload, autoPaging, config.autoPageDir, (routes.get("APP_TSX") || ""), (f, p) => {
744
- routes.set(`GET:${p}`, (req, res) => {
745
- res.setHeader("Content-Type", "application/javascript");
1083
+ routes.set(`GET:${p}`, (_, res) => {
1084
+ res.setHeader("Content-Type", p.toLowerCase().endsWith(".map") ? "application/json" : "application/javascript");
746
1085
  res.end(f);
747
1086
  });
748
- }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat);
1087
+ }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat, config_.renderType, config_.patchForSsr);
749
1088
  html = html.replace("<script>", "<script>\nwindow.__paused__ = true;");
750
1089
  res.setHeader("Content-Type", "text/html");
751
1090
  return res.end(html);
752
1091
  }
753
1092
  catch (e) {
754
- console.error("[authtics] renderPage error:", e.message);
1093
+ console.error("[HostJS] renderPage error:", e.message);
755
1094
  res.writeHead(500);
756
1095
  return res.end(`<html><body><pre>500 | Render error:\n${e.message}</pre></body></html>`);
757
1096
  }
@@ -764,17 +1103,17 @@ async function start() {
764
1103
  if (fs.existsSync(appPath)) {
765
1104
  try {
766
1105
  let html = await renderPage(appPath, url, actuallyShowPanel, compiler, hotReload, autoPaging, config.autoPageDir, (routes.get("APP_TSX") || ""), (f, p) => {
767
- routes.set(`GET:${p}`, (req, res) => {
768
- res.setHeader("Content-Type", "application/javascript");
1106
+ routes.set(`GET:${p}`, (_, res) => {
1107
+ res.setHeader("Content-Type", p.toLowerCase().endsWith(".map") ? "application/json" : "application/javascript");
769
1108
  res.end(f);
770
1109
  });
771
- }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat);
1110
+ }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat, config_.renderType, config_.patchForSsr);
772
1111
  html = html.replace("<script>", "<script>\nwindow.__paused__ = true;");
773
1112
  res.setHeader("Content-Type", "text/html");
774
1113
  return res.end(html);
775
1114
  }
776
1115
  catch (e) {
777
- console.error("[authtics] renderPage error:", e.message);
1116
+ console.error("[HostJS] renderPage error:", e.message);
778
1117
  res.writeHead(500);
779
1118
  return res.end(`<html><body><pre>500 | Render error:\n${e.message}</pre></body></html>`);
780
1119
  }
@@ -808,12 +1147,12 @@ async function start() {
808
1147
  const handler = apiModule.default ?? apiModule.handler ?? null;
809
1148
  if (typeof handler !== 'function') {
810
1149
  res.writeHead(500);
811
- return res.end(`[authtics] API route ${pathname} must export a default function`);
1150
+ return res.end(`[HostJS] API route ${pathname} must export a default function`);
812
1151
  }
813
1152
  return handler(req, res);
814
1153
  }
815
1154
  catch (e) {
816
- console.error(`[authtics] API route error (${pathname}):`, e.message);
1155
+ console.error(`[HostJS] API route error (${pathname}):`, e.message);
817
1156
  res.writeHead(500);
818
1157
  return res.end(JSON.stringify({ error: e.message }));
819
1158
  }
@@ -822,11 +1161,11 @@ async function start() {
822
1161
  if (fs.existsSync(notFoundPath)) {
823
1162
  try {
824
1163
  let html = await renderPage(notFoundPath, url, actuallyShowPanel, compiler, hotReload, autoPaging, config.autoPageDir, (routes.get("APP_TSX") || ""), (f, p) => {
825
- routes.set(`GET:${p}`, (req, res) => {
826
- res.setHeader("Content-Type", "application/javascript");
1164
+ routes.set(`GET:${p}`, (_, res) => {
1165
+ res.setHeader("Content-Type", p.toLowerCase().endsWith(".map") ? "application/json" : "application/javascript");
827
1166
  res.end(f);
828
1167
  });
829
- }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat);
1168
+ }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat, config_.renderType, config_.patchForSsr);
830
1169
  res.setHeader("Content-Type", "text/html");
831
1170
  res.writeHead(404);
832
1171
  html = html.replace("<script>", "<script>\nwindow.__status_code__ = 404;");
@@ -845,14 +1184,24 @@ async function start() {
845
1184
  const urlPath = pathname === "/" ? "/index" : pathname;
846
1185
  const tsxPath = pagesDir ? path.join(pagesDir, `${urlPath}.tsx`) : undefined;
847
1186
  const htmlPath = pagesDir ? path.join(pagesDir, `${urlPath}.html`) : undefined;
1187
+ if (srtws)
1188
+ deleteSrtwsServer();
1189
+ if (fs.existsSync(tempFileForTsx(tsxPath || "")) && renderType === "srtws" && !srtws) {
1190
+ srtws = handleSrtwsConnection(server, tsxPath || "", config, url, req.cookies.dat, (p) => { routes.delete(`GET:${p}`); }, (f, p) => {
1191
+ routes.set(`GET:${p}`, (_, res) => {
1192
+ res.setHeader("Content-Type", p.toLowerCase().endsWith(".map") ? "application/json" : "application/javascript");
1193
+ res.end(f);
1194
+ });
1195
+ });
1196
+ }
848
1197
  if (tsxPath && fs.existsSync(tsxPath)) {
849
1198
  try {
850
1199
  const html = await renderPage(tsxPath, url, actuallyShowPanel, compiler, hotReload, autoPaging, config.autoPageDir, (routes.get("APP_TSX") || ""), (f, p) => {
851
- routes.set(`GET:${p}`, (req, res) => {
852
- res.setHeader("Content-Type", "application/javascript");
1200
+ routes.set(`GET:${p}`, (_, res) => {
1201
+ res.setHeader("Content-Type", p.toLowerCase().endsWith(".map") ? "application/json" : "application/javascript");
853
1202
  res.end(f);
854
1203
  });
855
- }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat);
1204
+ }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat, config_.renderType, config_.patchForSsr);
856
1205
  res.setHeader("Content-Type", "text/html");
857
1206
  return res.end(html);
858
1207
  }
@@ -863,8 +1212,9 @@ async function start() {
863
1212
  }
864
1213
  }
865
1214
  if (htmlPath && fs.existsSync(htmlPath)) {
866
- res.setHeader("Content-Type", "text/html");
867
- return res.end(fs.readFileSync(htmlPath, "utf-8"));
1215
+ const html = await handleHTML(fs.readFileSync(htmlPath, "utf-8"), actuallyShowPanel, config_.dat, req.cookies.dat, hotReload);
1216
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
1217
+ return res.end(html);
868
1218
  }
869
1219
  if (routes.has("APP_TSX")) {
870
1220
  const appTsxPath = routes.get("APP_TSX");
@@ -872,11 +1222,11 @@ async function start() {
872
1222
  if (fs.existsSync(appPath)) {
873
1223
  try {
874
1224
  const html = await renderPage(appPath, url, actuallyShowPanel, compiler, hotReload, autoPaging, config.autoPageDir, (routes.get("APP_TSX") || ""), (f, p) => {
875
- routes.set(`GET:${p}`, (req, res) => {
876
- res.setHeader("Content-Type", "application/javascript");
1225
+ routes.set(`GET:${p}`, (_, res) => {
1226
+ res.setHeader("Content-Type", p.toLowerCase().endsWith(".map") ? "application/json" : "application/javascript");
877
1227
  res.end(f);
878
1228
  });
879
- }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat);
1229
+ }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat, config_.renderType, config_.patchForSsr);
880
1230
  res.setHeader("Content-Type", "text/html");
881
1231
  return res.end(html);
882
1232
  }
@@ -891,11 +1241,11 @@ async function start() {
891
1241
  if (fs.existsSync(notFoundPath)) {
892
1242
  try {
893
1243
  let html = await renderPage(notFoundPath, url, actuallyShowPanel, compiler, hotReload, autoPaging, config.autoPageDir, (routes.get("APP_TSX") || ""), (f, p) => {
894
- routes.set(`GET:${p}`, (req, res) => {
895
- res.setHeader("Content-Type", "application/javascript");
1244
+ routes.set(`GET:${p}`, (_, res) => {
1245
+ res.setHeader("Content-Type", p.toLowerCase().endsWith(".map") ? "application/json" : "application/javascript");
896
1246
  res.end(f);
897
1247
  });
898
- }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat);
1248
+ }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat, config_.renderType, config_.patchForSsr);
899
1249
  res.setHeader("Content-Type", "text/html");
900
1250
  res.writeHead(404);
901
1251
  html = html.replace("<script>", "<script>\nwindow.__status_code__ = 404;");
@@ -934,7 +1284,17 @@ async function start() {
934
1284
  if (fs.existsSync(_404Built))
935
1285
  watchSet.add(_404Built);
936
1286
  for (const dir of watchSet) {
937
- safeWatch(dir, notifyHmrClients);
1287
+ safeWatch(dir, () => {
1288
+ if (buildStyle === "super") {
1289
+ try {
1290
+ refreshSuperRouteManifest(process.cwd(), config, pagesDir, publicDir);
1291
+ }
1292
+ catch (error) {
1293
+ console.error("[HostJS] Failed to refresh Super route manifest:", error);
1294
+ }
1295
+ }
1296
+ notifyHmrClients();
1297
+ });
938
1298
  }
939
1299
  }
940
1300
  return server;