@bananacool467/authtics-host 0.2.6 → 0.2.8

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,11 +1,13 @@
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, handleSrtwsConnection } from "./renderEngine.js";
8
- import { tempFileForTsx } from "./rendererTempFiles.js";
8
+ import { renderPage, handleSrtwsConnection, deleteSrtwsServer, handleHTML } from "./renderEngine.js";
9
+ import { setBuildStyle, tempFileForTsx } from "./rendererTempFiles.js";
10
+ import { initializeSuperBuildOutput, refreshSuperRouteManifest } from "./superBuildOutput.js";
9
11
  let config_ = {};
10
12
  let uptime = 0;
11
13
  let totalRequests = 0;
@@ -17,12 +19,174 @@ const routes = new Map();
17
19
  const __filename = url.fileURLToPath(import.meta.url);
18
20
  const __dirname = path.dirname(__filename);
19
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
+ }
20
174
  function safeWatch(target, onChange) {
21
175
  const shouldReact = (filename) => {
22
176
  if (!filename)
23
177
  return false;
24
178
  if (filename.includes('.authtics_host'))
25
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;
26
190
  if (filename.includes('node_modules'))
27
191
  return false;
28
192
  return /\.(tsx?|jsx?|html|css)$/i.test(filename);
@@ -69,20 +233,89 @@ function notifyHmrClients() {
69
233
  hmrDebounceTimer = setTimeout(() => {
70
234
  hmrDebounceTimer = null;
71
235
  for (const client of hmrClients) {
236
+ if (client.destroyed || client.writableEnded) {
237
+ hmrClients.delete(client);
238
+ continue;
239
+ }
72
240
  try {
73
241
  client.write('data: reload\n\n');
74
242
  }
75
243
  catch {
76
244
  hmrClients.delete(client);
245
+ client.destroy();
77
246
  }
78
247
  }
79
248
  }, 120);
80
249
  }
81
250
  const MIME_TYPES = {
82
251
  '.html': 'text/html',
252
+ '.htm': 'text/html',
83
253
  '.css': 'text/css',
84
254
  '.js': 'application/javascript',
255
+ '.mjs': 'application/javascript',
256
+ '.jsx': 'text/jsx',
257
+ '.ts': 'text/typescript',
258
+ '.tsx': 'text/tsx',
85
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',
86
319
  '.png': 'image/png',
87
320
  '.jpg': 'image/jpeg',
88
321
  '.jpeg': 'image/jpeg',
@@ -90,13 +323,49 @@ const MIME_TYPES = {
90
323
  '.svg': 'image/svg+xml',
91
324
  '.ico': 'image/x-icon',
92
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',
93
347
  '.woff': 'font/woff',
94
348
  '.woff2': 'font/woff2',
95
349
  '.ttf': 'font/ttf',
96
- '.mp4': 'video/mp4',
97
- '.mp3': 'audio/mpeg',
98
- '.txt': 'text/plain',
99
- '.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',
100
369
  };
101
370
  const getCpu = () => {
102
371
  return new Promise((resolve) => {
@@ -260,7 +529,7 @@ export default class App {
260
529
  stdio: 'inherit'
261
530
  });
262
531
  child.unref();
263
- this.server.close(() => {
532
+ this.server?.close(() => {
264
533
  process.kill(process.pid, 'SIGTERM');
265
534
  });
266
535
  }
@@ -274,7 +543,7 @@ export default class App {
274
543
  if (datMatched) {
275
544
  res.writeHead(200, { "Content-Type": "application/json" });
276
545
  res.end(JSON.stringify({ message: "Shutting down..." }));
277
- this.server.close(() => {
546
+ this.server?.close(() => {
278
547
  process.kill(process.pid, 'SIGTERM');
279
548
  });
280
549
  }
@@ -390,15 +659,15 @@ export default class App {
390
659
  this.server = await start();
391
660
  }
392
661
  catch (error) {
393
- console.error("Server failed to start:", error);
662
+ console.error("[HostJS] Server failed to start: ", error);
394
663
  }
395
664
  }
396
- get(routePath, handler) {
397
- routes.set(`GET:${routePath}`, handler);
398
- }
399
665
  registerApp(route) {
400
666
  routes.set(`APP_TSX`, route);
401
667
  }
668
+ get(routePath, handler) {
669
+ routes.set(`GET:${routePath}`, handler);
670
+ }
402
671
  post(routePath, handler) {
403
672
  routes.set(`POST:${routePath}`, handler);
404
673
  }
@@ -408,6 +677,15 @@ export default class App {
408
677
  delete(routePath, handler) {
409
678
  routes.set(`DELETE:${routePath}`, handler);
410
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
+ }
411
689
  remove(method, routePath) {
412
690
  if (routes.has(`${method}:${routePath}`))
413
691
  routes.delete(`${method}:${routePath}`);
@@ -422,6 +700,12 @@ async function start() {
422
700
  }
423
701
  catch {
424
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());
425
709
  const PORT = config.port ?? 3000;
426
710
  const renderType = config.renderType ?? 'ssr';
427
711
  const showPanel = config.devMode !== false;
@@ -431,6 +715,9 @@ async function start() {
431
715
  const pagesDir = config.autoPage === true ? path.join(process.cwd(), config.autoPageDir || "") : undefined;
432
716
  const publicDir = config.autoFile === true ? path.join(process.cwd(), config.autoFileDir || "") : undefined;
433
717
  const apiDir = pagesDir ? path.join(pagesDir, "api") : undefined;
718
+ if (buildStyle === "super") {
719
+ initializeSuperBuildOutput(process.cwd(), config, pagesDir, publicDir);
720
+ }
434
721
  const server = createServer(async (req, res) => {
435
722
  const { url = "/", method } = req;
436
723
  const actuallyShowPanel = showPanel;
@@ -542,8 +829,17 @@ async function start() {
542
829
  res.writeHead(302, { Location: req.url + '/' });
543
830
  res.end();
544
831
  };
545
- res.setCookie = function (name, value) {
546
- 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("; "));
547
843
  };
548
844
  res.rmvCookie = function (name) {
549
845
  res.setHeader("Set-Cookie", `${name}=; Path=/; Max-Age=0`);
@@ -553,9 +849,28 @@ async function start() {
553
849
  pathname = safePath;
554
850
  if (pathname !== "/" && pathname.endsWith("/"))
555
851
  pathname = pathname.slice(0, -1);
556
- if (!pathname.includes('/__authtics') && !pathname.includes('/hmr')) {
852
+ if (!pathname.includes('/__authtics') && !pathname.includes('/hmr') && !pathname.includes('/srtws')) {
557
853
  totalRequests++;
558
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
+ }
559
874
  const routeKey = `${method}:${pathname}`;
560
875
  if (routes.has(routeKey)) {
561
876
  if (Array.isArray(config_.dat) && !config_.dat.includes(req.dat) && usersPaused && !routeKey.endsWith('.js')) {
@@ -564,8 +879,8 @@ async function start() {
564
879
  if (fs.existsSync(pausedTsxFile || "")) {
565
880
  try {
566
881
  let html = await renderPage(pausedTsxFile || "", url, actuallyShowPanel, compiler, hotReload, autoPaging, config.autoPageDir, (routes.get("APP_TSX") || ""), (f, p) => {
567
- routes.set(`GET:${p}`, (req, res) => {
568
- 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");
569
884
  res.end(f);
570
885
  });
571
886
  }, (p) => { routes.delete(`GET:${p}`); }, req.dat, config_.dat, config_.renderType, config_.patchForSsr);
@@ -574,7 +889,7 @@ async function start() {
574
889
  return res.end(html);
575
890
  }
576
891
  catch (e) {
577
- console.error("[authtics] renderPage error:", e.message);
892
+ console.error("[HostJS] renderPage error:", e.message);
578
893
  res.writeHead(500);
579
894
  return res.end(`<html><body><pre>500 | Render error:\n${e.message}</pre></body></html>`);
580
895
  }
@@ -587,8 +902,8 @@ async function start() {
587
902
  if (fs.existsSync(appPath)) {
588
903
  try {
589
904
  let html = await renderPage(appPath, url, actuallyShowPanel, compiler, hotReload, autoPaging, config.autoPageDir, (routes.get("APP_TSX") || ""), (f, p) => {
590
- routes.set(`GET:${p}`, (req, res) => {
591
- 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");
592
907
  res.end(f);
593
908
  });
594
909
  }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat, config_.renderType);
@@ -597,7 +912,7 @@ async function start() {
597
912
  return res.end(html);
598
913
  }
599
914
  catch (e) {
600
- console.error("[authtics] renderPage error:", e.message);
915
+ console.error("[HostJS] renderPage error:", e.message);
601
916
  res.writeHead(500);
602
917
  return res.end(`<html><body><pre>500 | Render error:\n${e.message}</pre></body></html>`);
603
918
  }
@@ -617,8 +932,8 @@ async function start() {
617
932
  if (fs.existsSync(pausedTsxFile || "")) {
618
933
  try {
619
934
  let html = await renderPage(pausedTsxFile || "", url, actuallyShowPanel, compiler, hotReload, autoPaging, config.autoPageDir, (routes.get("APP_TSX") || ""), (f, p) => {
620
- routes.set(`GET:${p}`, (req, res) => {
621
- 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");
622
937
  res.end(f);
623
938
  });
624
939
  }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat, config_.renderType, config_.patchForSsr);
@@ -627,7 +942,7 @@ async function start() {
627
942
  return res.end(html);
628
943
  }
629
944
  catch (e) {
630
- console.error("[authtics] renderPage error:", e.message);
945
+ console.error("[HostJS] renderPage error:", e.message);
631
946
  res.writeHead(500);
632
947
  return res.end(`<html><body><pre>500 | Render error:\n${e.message}</pre></body></html>`);
633
948
  }
@@ -640,8 +955,8 @@ async function start() {
640
955
  if (fs.existsSync(appPath)) {
641
956
  try {
642
957
  let html = await renderPage(appPath, url, actuallyShowPanel, compiler, hotReload, autoPaging, config.autoPageDir, (routes.get("APP_TSX") || ""), (f, p) => {
643
- routes.set(`GET:${p}`, (req, res) => {
644
- 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");
645
960
  res.end(f);
646
961
  });
647
962
  }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat, config_.renderType);
@@ -650,7 +965,7 @@ async function start() {
650
965
  return res.end(html);
651
966
  }
652
967
  catch (e) {
653
- console.error("[authtics] renderPage error:", e.message);
968
+ console.error("[HostJS] renderPage error:", e.message);
654
969
  res.writeHead(500);
655
970
  return res.end(`<html><body><pre>500 | Render error:\n${e.message}</pre></body></html>`);
656
971
  }
@@ -665,6 +980,17 @@ async function start() {
665
980
  }));
666
981
  }
667
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
+ }
668
994
  if (typeof handler === 'string') {
669
995
  return;
670
996
  }
@@ -675,17 +1001,24 @@ async function start() {
675
1001
  if (pathname === '/hmr') {
676
1002
  res.writeHead(200, {
677
1003
  'Content-Type': 'text/event-stream',
678
- 'Cache-Control': 'no-cache',
1004
+ 'Cache-Control': 'no-cache, no-transform',
679
1005
  'Connection': 'keep-alive',
680
1006
  'Access-Control-Allow-Origin': '*',
681
1007
  });
682
1008
  res.write('data: connected\n\n');
683
1009
  hmrClients.add(res);
684
1010
  users++;
685
- 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);
686
1019
  return;
687
1020
  }
688
- if (pathname === '/__authtics_srtws__') {
1021
+ if (pathname === '/srtws') {
689
1022
  return;
690
1023
  }
691
1024
  if (Array.isArray(config_.dat) && !config_.dat.includes(req.dat) && usersPaused) {
@@ -694,8 +1027,8 @@ async function start() {
694
1027
  if (fs.existsSync(pausedTsxFile || "")) {
695
1028
  try {
696
1029
  let html = await renderPage(pausedTsxFile || "", url, actuallyShowPanel, compiler, hotReload, autoPaging, config.autoPageDir, (routes.get("APP_TSX") || ""), (f, p) => {
697
- routes.set(`GET:${p}`, (req, res) => {
698
- 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");
699
1032
  res.end(f);
700
1033
  });
701
1034
  }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat, config_.renderType, config_.patchForSsr);
@@ -704,7 +1037,7 @@ async function start() {
704
1037
  return res.end(html);
705
1038
  }
706
1039
  catch (e) {
707
- console.error("[authtics] renderPage error:", e.message);
1040
+ console.error("[HostJS] renderPage error:", e.message);
708
1041
  res.writeHead(500);
709
1042
  return res.end(`<html><body><pre>500 | Render error:\n${e.message}</pre></body></html>`);
710
1043
  }
@@ -717,8 +1050,8 @@ async function start() {
717
1050
  if (fs.existsSync(appPath)) {
718
1051
  try {
719
1052
  let html = await renderPage(appPath, url, actuallyShowPanel, compiler, hotReload, autoPaging, config.autoPageDir, (routes.get("APP_TSX") || ""), (f, p) => {
720
- routes.set(`GET:${p}`, (req, res) => {
721
- 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");
722
1055
  res.end(f);
723
1056
  });
724
1057
  }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat, config_.renderType, config_.patchForSsr);
@@ -727,7 +1060,7 @@ async function start() {
727
1060
  return res.end(html);
728
1061
  }
729
1062
  catch (e) {
730
- console.error("[authtics] renderPage error:", e.message);
1063
+ console.error("[HostJS] renderPage error:", e.message);
731
1064
  res.writeHead(500);
732
1065
  return res.end(`<html><body><pre>500 | Render error:\n${e.message}</pre></body></html>`);
733
1066
  }
@@ -747,8 +1080,8 @@ async function start() {
747
1080
  if (fs.existsSync(pausedTsxFile || "")) {
748
1081
  try {
749
1082
  let html = await renderPage(pausedTsxFile || "", url, actuallyShowPanel, compiler, hotReload, autoPaging, config.autoPageDir, (routes.get("APP_TSX") || ""), (f, p) => {
750
- routes.set(`GET:${p}`, (req, res) => {
751
- 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");
752
1085
  res.end(f);
753
1086
  });
754
1087
  }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat, config_.renderType, config_.patchForSsr);
@@ -757,7 +1090,7 @@ async function start() {
757
1090
  return res.end(html);
758
1091
  }
759
1092
  catch (e) {
760
- console.error("[authtics] renderPage error:", e.message);
1093
+ console.error("[HostJS] renderPage error:", e.message);
761
1094
  res.writeHead(500);
762
1095
  return res.end(`<html><body><pre>500 | Render error:\n${e.message}</pre></body></html>`);
763
1096
  }
@@ -770,8 +1103,8 @@ async function start() {
770
1103
  if (fs.existsSync(appPath)) {
771
1104
  try {
772
1105
  let html = await renderPage(appPath, url, actuallyShowPanel, compiler, hotReload, autoPaging, config.autoPageDir, (routes.get("APP_TSX") || ""), (f, p) => {
773
- routes.set(`GET:${p}`, (req, res) => {
774
- 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");
775
1108
  res.end(f);
776
1109
  });
777
1110
  }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat, config_.renderType, config_.patchForSsr);
@@ -780,7 +1113,7 @@ async function start() {
780
1113
  return res.end(html);
781
1114
  }
782
1115
  catch (e) {
783
- console.error("[authtics] renderPage error:", e.message);
1116
+ console.error("[HostJS] renderPage error:", e.message);
784
1117
  res.writeHead(500);
785
1118
  return res.end(`<html><body><pre>500 | Render error:\n${e.message}</pre></body></html>`);
786
1119
  }
@@ -814,12 +1147,12 @@ async function start() {
814
1147
  const handler = apiModule.default ?? apiModule.handler ?? null;
815
1148
  if (typeof handler !== 'function') {
816
1149
  res.writeHead(500);
817
- 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`);
818
1151
  }
819
1152
  return handler(req, res);
820
1153
  }
821
1154
  catch (e) {
822
- console.error(`[authtics] API route error (${pathname}):`, e.message);
1155
+ console.error(`[HostJS] API route error (${pathname}):`, e.message);
823
1156
  res.writeHead(500);
824
1157
  return res.end(JSON.stringify({ error: e.message }));
825
1158
  }
@@ -828,8 +1161,8 @@ async function start() {
828
1161
  if (fs.existsSync(notFoundPath)) {
829
1162
  try {
830
1163
  let html = await renderPage(notFoundPath, url, actuallyShowPanel, compiler, hotReload, autoPaging, config.autoPageDir, (routes.get("APP_TSX") || ""), (f, p) => {
831
- routes.set(`GET:${p}`, (req, res) => {
832
- 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");
833
1166
  res.end(f);
834
1167
  });
835
1168
  }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat, config_.renderType, config_.patchForSsr);
@@ -851,15 +1184,21 @@ async function start() {
851
1184
  const urlPath = pathname === "/" ? "/index" : pathname;
852
1185
  const tsxPath = pagesDir ? path.join(pagesDir, `${urlPath}.tsx`) : undefined;
853
1186
  const htmlPath = pagesDir ? path.join(pagesDir, `${urlPath}.html`) : undefined;
854
- if (fs.existsSync(tempFileForTsx(tsxPath || ""))) {
855
- const { default: PageComponent } = await import(tempFileForTsx(tsxPath || "").replace(/\.json$/, ".server.cjs"));
856
- srtws = handleSrtwsConnection(server, PageComponent);
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
+ });
857
1196
  }
858
1197
  if (tsxPath && fs.existsSync(tsxPath)) {
859
1198
  try {
860
1199
  const html = await renderPage(tsxPath, url, actuallyShowPanel, compiler, hotReload, autoPaging, config.autoPageDir, (routes.get("APP_TSX") || ""), (f, p) => {
861
- routes.set(`GET:${p}`, (req, res) => {
862
- 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");
863
1202
  res.end(f);
864
1203
  });
865
1204
  }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat, config_.renderType, config_.patchForSsr);
@@ -873,8 +1212,9 @@ async function start() {
873
1212
  }
874
1213
  }
875
1214
  if (htmlPath && fs.existsSync(htmlPath)) {
876
- res.setHeader("Content-Type", "text/html");
877
- 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);
878
1218
  }
879
1219
  if (routes.has("APP_TSX")) {
880
1220
  const appTsxPath = routes.get("APP_TSX");
@@ -882,8 +1222,8 @@ async function start() {
882
1222
  if (fs.existsSync(appPath)) {
883
1223
  try {
884
1224
  const html = await renderPage(appPath, url, actuallyShowPanel, compiler, hotReload, autoPaging, config.autoPageDir, (routes.get("APP_TSX") || ""), (f, p) => {
885
- routes.set(`GET:${p}`, (req, res) => {
886
- 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");
887
1227
  res.end(f);
888
1228
  });
889
1229
  }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat, config_.renderType, config_.patchForSsr);
@@ -901,8 +1241,8 @@ async function start() {
901
1241
  if (fs.existsSync(notFoundPath)) {
902
1242
  try {
903
1243
  let html = await renderPage(notFoundPath, url, actuallyShowPanel, compiler, hotReload, autoPaging, config.autoPageDir, (routes.get("APP_TSX") || ""), (f, p) => {
904
- routes.set(`GET:${p}`, (req, res) => {
905
- 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");
906
1246
  res.end(f);
907
1247
  });
908
1248
  }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat, config_.renderType, config_.patchForSsr);
@@ -944,7 +1284,17 @@ async function start() {
944
1284
  if (fs.existsSync(_404Built))
945
1285
  watchSet.add(_404Built);
946
1286
  for (const dir of watchSet) {
947
- 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
+ });
948
1298
  }
949
1299
  }
950
1300
  return server;