@bananacool467/authtics-host 0.0.1-beta.9 â 0.0.2-beta.2
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/README.md +1 -3
- package/dist/cli.js +5 -65
- package/dist/devPanel.js +7 -277
- package/dist/renderEngine.js +3 -57
- package/dist/rendererTempFiles.d.ts +0 -13
- package/dist/rendererTempFiles.js +0 -23
- package/package.json +11 -9
package/README.md
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
# Authtics Host
|
|
2
2
|
|
|
3
|
-
Version 0.0.
|
|
3
|
+
Version 0.0.2-beta.2
|
|
4
4
|
License: MIT
|
|
5
5
|
|
|
6
|
-
**(UPDATE (Security Update): Now versions, 0.0.1-beta.9 and over now requires DAT (Developer Access Token) restriction to access remote controls such as: Restarting and Shutting Down)**
|
|
7
|
-
|
|
8
6
|
**(NOTE: This version is in BETA, bugs are expected)**
|
|
9
7
|
|
|
10
8
|
Authtics Host is a framework inspired by Next.js. Authtics Host supports SSR (Server-Side Rendering) and uses esbuild.
|
package/dist/cli.js
CHANGED
|
@@ -5,7 +5,6 @@ import path from "node:path";
|
|
|
5
5
|
import fs from "node:fs";
|
|
6
6
|
import url from "node:url";
|
|
7
7
|
import { renderPage } from "./renderEngine.js";
|
|
8
|
-
import tls from 'tls';
|
|
9
8
|
let config_ = {};
|
|
10
9
|
let uptime = 0;
|
|
11
10
|
let totalRequests = 0;
|
|
@@ -14,7 +13,6 @@ const jiti = createJiti(import.meta.url);
|
|
|
14
13
|
const routes = new Map();
|
|
15
14
|
const __filename = url.fileURLToPath(import.meta.url);
|
|
16
15
|
const __dirname = path.dirname(__filename);
|
|
17
|
-
// SSE clients waiting for hot reload signals
|
|
18
16
|
const hmrClients = new Set();
|
|
19
17
|
function notifyHmrClients() {
|
|
20
18
|
for (const client of hmrClients) {
|
|
@@ -25,11 +23,7 @@ function notifyHmrClients() {
|
|
|
25
23
|
hmrClients.delete(client);
|
|
26
24
|
}
|
|
27
25
|
}
|
|
28
|
-
//console.log(`[authtics] hot reload triggered (${hmrClients.size} client(s))`);
|
|
29
26
|
}
|
|
30
|
-
// ---------------------------------------------------------------------------
|
|
31
|
-
// MIME types for static files in public/
|
|
32
|
-
// ---------------------------------------------------------------------------
|
|
33
27
|
const MIME_TYPES = {
|
|
34
28
|
'.html': 'text/html',
|
|
35
29
|
'.css': 'text/css',
|
|
@@ -50,9 +44,6 @@ const MIME_TYPES = {
|
|
|
50
44
|
'.txt': 'text/plain',
|
|
51
45
|
'.pdf': 'application/pdf',
|
|
52
46
|
};
|
|
53
|
-
// ---------------------------------------------------------------------------
|
|
54
|
-
// Augment ServerResponse with helper methods
|
|
55
|
-
// ---------------------------------------------------------------------------
|
|
56
47
|
function augmentRes(res) {
|
|
57
48
|
const r = res;
|
|
58
49
|
r.html = (html) => { res.setHeader("Content-Type", "text/html"); res.end(html); };
|
|
@@ -73,13 +64,11 @@ const getCpu = () => {
|
|
|
73
64
|
};
|
|
74
65
|
async function getJsonBody(req) {
|
|
75
66
|
return new Promise((resolve, reject) => {
|
|
76
|
-
// 1. Immediate exit for methods that shouldn't have a body
|
|
77
67
|
const hasBody = req.headers['content-length'] || req.headers['transfer-encoding'];
|
|
78
68
|
if (!hasBody && (req.method === 'GET' || req.method === 'DELETE')) {
|
|
79
69
|
return resolve({});
|
|
80
70
|
}
|
|
81
71
|
const chunks = [];
|
|
82
|
-
// 2. Safety Timeout: If it takes more than 5s, kill the request
|
|
83
72
|
const timeout = setTimeout(() => {
|
|
84
73
|
req.destroy();
|
|
85
74
|
reject(new Error('Request Timeout: Body took too long to arrive'));
|
|
@@ -103,9 +92,6 @@ async function getJsonBody(req) {
|
|
|
103
92
|
});
|
|
104
93
|
});
|
|
105
94
|
}
|
|
106
|
-
// ---------------------------------------------------------------------------
|
|
107
|
-
// App class
|
|
108
|
-
// ---------------------------------------------------------------------------
|
|
109
95
|
export default class App {
|
|
110
96
|
server;
|
|
111
97
|
constructor() {
|
|
@@ -210,9 +196,7 @@ export default class App {
|
|
|
210
196
|
stdio: 'inherit'
|
|
211
197
|
});
|
|
212
198
|
child.unref();
|
|
213
|
-
//this.server.close(() => {
|
|
214
199
|
process.kill(process.pid, 'SIGTERM');
|
|
215
|
-
//})
|
|
216
200
|
}
|
|
217
201
|
});
|
|
218
202
|
this.post("/__authtics_host_apis__/shutdown", async (req, res) => {
|
|
@@ -329,22 +313,10 @@ export default class App {
|
|
|
329
313
|
if (config_.dat && Array.isArray(config_.dat) && config_.dat.includes(reqBody.dat))
|
|
330
314
|
datMatched = true;
|
|
331
315
|
if (datMatched) {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
res.end(JSON.stringify({
|
|
337
|
-
protocol,
|
|
338
|
-
cipher: cipher.name,
|
|
339
|
-
httpVersion: req.httpVersion
|
|
340
|
-
}));
|
|
341
|
-
}
|
|
342
|
-
else {
|
|
343
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
344
|
-
res.end(JSON.stringify({
|
|
345
|
-
httpVersion: req.httpVersion
|
|
346
|
-
}));
|
|
347
|
-
}
|
|
316
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
317
|
+
res.end(JSON.stringify({
|
|
318
|
+
httpVersion: req.httpVersion
|
|
319
|
+
}));
|
|
348
320
|
}
|
|
349
321
|
else {
|
|
350
322
|
const { url = "/", method } = req;
|
|
@@ -388,11 +360,7 @@ export default class App {
|
|
|
388
360
|
routes.set(`DELETE:${routePath}`, handler);
|
|
389
361
|
}
|
|
390
362
|
}
|
|
391
|
-
// ---------------------------------------------------------------------------
|
|
392
|
-
// Server
|
|
393
|
-
// ---------------------------------------------------------------------------
|
|
394
363
|
async function start() {
|
|
395
|
-
//console.log("đ ī¸ Initializing Authtics Host...");
|
|
396
364
|
let config = {};
|
|
397
365
|
try {
|
|
398
366
|
const configPath = path.join(process.cwd(), "authtics_host.config.ts");
|
|
@@ -400,7 +368,6 @@ async function start() {
|
|
|
400
368
|
config_ = config;
|
|
401
369
|
}
|
|
402
370
|
catch {
|
|
403
|
-
//console.log("âšī¸ No config found, using defaults.");
|
|
404
371
|
}
|
|
405
372
|
if (config.autoPage == null)
|
|
406
373
|
config.autoPage = true;
|
|
@@ -416,21 +383,15 @@ async function start() {
|
|
|
416
383
|
const publicDir = config.autoFile === true ? path.join(process.cwd(), config.autoFileDir || "") : undefined;
|
|
417
384
|
const apiDir = pagesDir ? path.join(pagesDir, "api") : undefined;
|
|
418
385
|
const server = createServer(async (req, res) => {
|
|
419
|
-
|
|
420
|
-
const actuallyShowPanel = showPanel; /* && (config.dat != undefined ? (body.dat == config.dat || (Array.isArray(config.dat) && config.dat.includes(body.dat))) : true)*/
|
|
386
|
+
const actuallyShowPanel = showPanel;
|
|
421
387
|
const r = augmentRes(res);
|
|
422
388
|
const { url = "/", method } = req;
|
|
423
|
-
// Strip query string, hash, trailing slash
|
|
424
389
|
let pathname = url.split("?")[0].split("#")[0];
|
|
425
390
|
if (pathname !== "/" && pathname.endsWith("/"))
|
|
426
391
|
pathname = pathname.slice(0, -1);
|
|
427
|
-
/*if (pathname !== "/__authtics_host_apis__/ping" && pathname !== "/__authtics_host_apis__/get-connection" && pathname !== "/__authtics_hmr__" && pathname !== "/__authtics_host_apis__/get-file-by-pathname" && pathname !== "/__authtics_host_apis__/get-is-ssr" && pathname !== "/__authtics_host_apis__/get-compiler" && pathname !== "/__authtics_host_apis__/restart" && pathname !== "/__authtics_host_apis__/shutdown" && pathname !== "/__authtics_host_apis__/get-server-info" && pathname !== "/__authtics_host_apis__/get-cpu_memory") {
|
|
428
|
-
totalRequests++;
|
|
429
|
-
}*/
|
|
430
392
|
if (!pathname.includes('/__authtics_host_apis__/')) {
|
|
431
393
|
totalRequests++;
|
|
432
394
|
}
|
|
433
|
-
// ââ 1. Registered routes (from app.get/post/etc.) ââââââââââââââââââââââ
|
|
434
395
|
const routeKey = `${method}:${pathname}`;
|
|
435
396
|
if (routes.has(routeKey)) {
|
|
436
397
|
const handler = routes.get(routeKey);
|
|
@@ -441,8 +402,6 @@ async function start() {
|
|
|
441
402
|
return handler(req, r);
|
|
442
403
|
}
|
|
443
404
|
}
|
|
444
|
-
// ââ 2. Hot reload SSE endpoint âââââââââââââââââââââââââââââââââââââââââ
|
|
445
|
-
// Browser connects here and waits for a 'reload' event
|
|
446
405
|
if (pathname === '/__authtics_hmr__') {
|
|
447
406
|
res.writeHead(200, {
|
|
448
407
|
'Content-Type': 'text/event-stream',
|
|
@@ -456,11 +415,8 @@ async function start() {
|
|
|
456
415
|
req.on('close', () => { hmrClients.delete(res); users--; });
|
|
457
416
|
return;
|
|
458
417
|
}
|
|
459
|
-
// ââ 3. Public folder â serves files from /public ââââââââââââââââââââââââ
|
|
460
|
-
// e.g. GET /logo.png â public/logo.png
|
|
461
418
|
if (publicDir && fs.existsSync(publicDir)) {
|
|
462
419
|
const staticPath = path.join(publicDir, pathname);
|
|
463
|
-
// Make sure the resolved path is still inside publicDir (prevent traversal)
|
|
464
420
|
const safePath = path.resolve(staticPath);
|
|
465
421
|
if (safePath.startsWith(path.resolve(publicDir)) && fs.existsSync(safePath) && fs.statSync(safePath).isFile()) {
|
|
466
422
|
const ext = path.extname(safePath).toLowerCase();
|
|
@@ -469,15 +425,12 @@ async function start() {
|
|
|
469
425
|
return fs.createReadStream(safePath).pipe(res);
|
|
470
426
|
}
|
|
471
427
|
}
|
|
472
|
-
// ââ 4. API routes â pages/api/*.ts files ââââââââââââââââââââââââââââââââ
|
|
473
|
-
// e.g. GET /api/users â pages/api/users.ts
|
|
474
428
|
if (pathname.startsWith("/api/") && pagesDir != null) {
|
|
475
429
|
const apiFile = path.join(pagesDir, `${pathname}.ts`);
|
|
476
430
|
const apiFileTsx = path.join(pagesDir, `${pathname}.tsx`);
|
|
477
431
|
const resolvedApi = fs.existsSync(apiFile) ? apiFile : fs.existsSync(apiFileTsx) ? apiFileTsx : null;
|
|
478
432
|
if (resolvedApi) {
|
|
479
433
|
try {
|
|
480
|
-
// Use jiti to import the API route handler on the fly
|
|
481
434
|
const apiModule = await jiti.import(resolvedApi, { default: true });
|
|
482
435
|
const handler = apiModule.default ?? apiModule.handler ?? null;
|
|
483
436
|
if (typeof handler !== 'function') {
|
|
@@ -492,11 +445,9 @@ async function start() {
|
|
|
492
445
|
return res.end(JSON.stringify({ error: e.message }));
|
|
493
446
|
}
|
|
494
447
|
}
|
|
495
|
-
// /api/ path but no matching file â 404
|
|
496
448
|
res.writeHead(404);
|
|
497
449
|
return res.end(JSON.stringify({ error: `API route not found: ${pathname}` }));
|
|
498
450
|
}
|
|
499
|
-
// ââ 5. TSX pages (SSR) ââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
500
451
|
const urlPath = pathname === "/" ? "/index" : pathname;
|
|
501
452
|
const tsxPath = pagesDir ? path.join(pagesDir, `${urlPath}.tsx`) : undefined;
|
|
502
453
|
const htmlPath = pagesDir ? path.join(pagesDir, `${urlPath}.html`) : undefined;
|
|
@@ -512,12 +463,10 @@ async function start() {
|
|
|
512
463
|
return res.end(`<html><body><pre>500 | Render error:\n${e.message}</pre></body></html>`);
|
|
513
464
|
}
|
|
514
465
|
}
|
|
515
|
-
// ââ 6. Static HTML pages ââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
516
466
|
if (htmlPath && fs.existsSync(htmlPath)) {
|
|
517
467
|
res.setHeader("Content-Type", "text/html");
|
|
518
468
|
return res.end(fs.readFileSync(htmlPath, "utf-8"));
|
|
519
469
|
}
|
|
520
|
-
// ââ 7. App.tsx file handling ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
521
470
|
if (routes.has("APP_TSX")) {
|
|
522
471
|
const appTsxPath = routes.get("APP_TSX");
|
|
523
472
|
const appPath = path.join(process.cwd(), appTsxPath);
|
|
@@ -534,7 +483,6 @@ async function start() {
|
|
|
534
483
|
}
|
|
535
484
|
}
|
|
536
485
|
}
|
|
537
|
-
// ââ 8. 404 ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
538
486
|
const notFoundPath = path.join(__dirname, '404.js');
|
|
539
487
|
if (fs.existsSync(notFoundPath)) {
|
|
540
488
|
try {
|
|
@@ -555,15 +503,12 @@ async function start() {
|
|
|
555
503
|
}
|
|
556
504
|
});
|
|
557
505
|
server.listen(PORT, () => {
|
|
558
|
-
//console.log(`đ Authtics Host live at http://localhost:${PORT}`);
|
|
559
506
|
setInterval(() => {
|
|
560
507
|
uptime++;
|
|
561
508
|
}, 1000);
|
|
562
509
|
});
|
|
563
|
-
// ââ Hot reload file watcher âââââââââââââââââââââââââââââââââââââââââââââ
|
|
564
510
|
if (config.hotReload === true) {
|
|
565
511
|
const watchDirs = [pagesDir];
|
|
566
|
-
// Also watch src/devPanel.tsx if it exists
|
|
567
512
|
const devPanelDir = path.join(__dirname, 'devPanel.js');
|
|
568
513
|
const _404Dir = path.join(__dirname, '404.js');
|
|
569
514
|
if (fs.existsSync(devPanelDir))
|
|
@@ -575,8 +520,6 @@ async function start() {
|
|
|
575
520
|
fs.watch(dir, { recursive: true }, (eventType, filename) => {
|
|
576
521
|
if (!filename)
|
|
577
522
|
return;
|
|
578
|
-
//console.log(`[authtics] file changed: ${filename}`);
|
|
579
|
-
// Clear the cache for any .tsx/.ts/.html file change
|
|
580
523
|
notifyHmrClients();
|
|
581
524
|
});
|
|
582
525
|
}
|
|
@@ -590,13 +533,10 @@ async function start() {
|
|
|
590
533
|
fs.watch(dir, { recursive: true }, (eventType, filename) => {
|
|
591
534
|
if (!filename)
|
|
592
535
|
return;
|
|
593
|
-
//console.log(`[authtics] file changed: ${filename}`);
|
|
594
|
-
// Clear the cache for any .tsx/.ts/.html file change
|
|
595
536
|
notifyHmrClients();
|
|
596
537
|
});
|
|
597
538
|
}
|
|
598
539
|
}
|
|
599
|
-
//console.log('đĨ Hot reload enabled â watching for file changes...');
|
|
600
540
|
}
|
|
601
541
|
return server;
|
|
602
542
|
}
|
package/dist/devPanel.js
CHANGED
|
@@ -1,31 +1,21 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import React, { useState, useEffect, useRef } from 'react';
|
|
3
3
|
import { ShieldCheck, CircleX, ChevronDown, ChevronUp, Bug, AlertTriangle, Info, ArrowLeft, Wifi, Terminal, Route, Globe, ChevronRight, ChevronLeft, Server } from 'lucide-react';
|
|
4
|
-
//import { isMobile } from 'react-device-detect';
|
|
5
4
|
import Prism from 'prismjs';
|
|
6
5
|
import 'prismjs/components/prism-javascript';
|
|
7
6
|
Prism.languages.javascript['builtin'] = /\b(window|document|console|navigator|localStorage|sessionStorage|fetch|Promise|setTimeout|setInterval|clearTimeout|clearInterval|requestAnimationFrame|Math|JSON|Object|Array|String|Number|Boolean|Date|RegExp|Error|Map|Set|Symbol|Proxy|Reflect|performance|history|location|screen|crypto|indexedDB|alert|confirm|prompt)\b/;
|
|
8
|
-
//const isMobile = true;
|
|
9
7
|
function getCallerSource() {
|
|
10
8
|
try {
|
|
11
9
|
const stack = new Error().stack ?? '';
|
|
12
10
|
const lines = stack.split('\n');
|
|
13
|
-
// Skip frames: Error, getCallerSource, the patched console[type], and installConsoleInterceptor
|
|
14
11
|
for (let i = 3; i < lines.length; i++) {
|
|
15
12
|
const line = lines[i];
|
|
16
|
-
|
|
17
|
-
if (
|
|
18
|
-
/*line.includes('installConsoleInterceptor') ||
|
|
19
|
-
line.includes('devPanel') ||
|
|
20
|
-
line.includes('<anonymous>') ||*/
|
|
21
|
-
line.trim() === '')
|
|
13
|
+
if (line.trim() === '')
|
|
22
14
|
continue;
|
|
23
|
-
// Extract filename:line from something like "at foo (http://localhost:5000/pages/index.tsx:42:10)"
|
|
24
15
|
const match = line.match(/\(?(https?:\/\/[^)]+)\)?$/) ??
|
|
25
16
|
line.match(/at\s+(https?:\/\/\S+)/);
|
|
26
17
|
if (match) {
|
|
27
18
|
const url = match[1];
|
|
28
|
-
// Turn full URL into just "filename:line"
|
|
29
19
|
const parts = url.replace(/\?.*/, '').split('/');
|
|
30
20
|
const file = parts[parts.length - 1];
|
|
31
21
|
const colonLine = url.match(/:(\d+):\d+$/);
|
|
@@ -33,17 +23,15 @@ function getCallerSource() {
|
|
|
33
23
|
}
|
|
34
24
|
}
|
|
35
25
|
}
|
|
36
|
-
catch {
|
|
26
|
+
catch { }
|
|
37
27
|
return '';
|
|
38
28
|
}
|
|
39
29
|
const LOG_TYPES = ['Log', 'Info', 'Warn', 'Error', 'Debug'];
|
|
40
|
-
const LOG_TYPE_2 = ['Eval'];
|
|
41
30
|
let fetchId = 0;
|
|
42
31
|
const TYPE_STYLE = {
|
|
43
32
|
Error: { bg: '#fff0f0', color: '#e53e3e', icon: _jsx(CircleX, { size: 11 }) },
|
|
44
33
|
Warn: { bg: '#fffbea', color: '#d97706', icon: _jsx(AlertTriangle, { size: 11 }) },
|
|
45
34
|
Log: { bg: '#ffffff', color: '#888', textColor: '#333', icon: null },
|
|
46
|
-
Eval: { bg: '#ffffff', color: '#888', textColor: '#333', icon: null },
|
|
47
35
|
Info: { bg: '#eff6ff', color: '#3b82f6', icon: _jsx(Info, { size: 11 }) },
|
|
48
36
|
Debug: { bg: '#f0fdf4', color: '#16a34a', icon: _jsx(Bug, { size: 11 }) },
|
|
49
37
|
};
|
|
@@ -51,7 +39,6 @@ const FILTER_COLORS = {
|
|
|
51
39
|
Error: '#e53e3e',
|
|
52
40
|
Warn: '#d97706',
|
|
53
41
|
Log: '#888',
|
|
54
|
-
Eval: '#888',
|
|
55
42
|
Info: '#3b82f6',
|
|
56
43
|
Debug: '#16a34a',
|
|
57
44
|
};
|
|
@@ -62,7 +49,6 @@ const TruncatedText = ({ content, id }) => {
|
|
|
62
49
|
: content;
|
|
63
50
|
return _jsx("div", { id: id, children: truncated });
|
|
64
51
|
};
|
|
65
|
-
// ââ Sparkline chart (SVG, no dependencies) ââââââââââââââââââââââââââââââââââ
|
|
66
52
|
function Sparkline({ data, color, label, unit }) {
|
|
67
53
|
const W = 280, H = 48;
|
|
68
54
|
const max = Math.max(...data, 1);
|
|
@@ -87,14 +73,11 @@ function installNetworkInterceptor(onFetch, setFetch, onBytesClient, onBytesServ
|
|
|
87
73
|
const savedFetchId = fetchId;
|
|
88
74
|
const url = typeof args[0] === 'string' ? args[0] : args[0].url;
|
|
89
75
|
const method = (args[1]?.method ?? 'GET').toUpperCase();
|
|
90
|
-
// Filter out DevPanel's own internal pings so they don't show up in the network tab
|
|
91
76
|
if (url.startsWith('/__authtics_host_apis__/') || url === "/__authtics_hmr__") {
|
|
92
77
|
return originalFetch(...args);
|
|
93
78
|
}
|
|
94
|
-
// Estimate bytes sent: body size + rough header/URL overhead
|
|
95
79
|
const body = args[1]?.body;
|
|
96
80
|
const bodyBytes = body ? new Blob([body]).size : 0;
|
|
97
|
-
// Headers + URL as a rough proxy for request overhead (like Chrome devtools does)
|
|
98
81
|
const headerStr = JSON.stringify(args[1]?.headers ?? {});
|
|
99
82
|
const sentBytes = bodyBytes + url.length + headerStr.length;
|
|
100
83
|
onBytesClient(sentBytes);
|
|
@@ -164,7 +147,7 @@ function PanelButton({ onClick, onClick2, children, style, fullWidth, disabled }
|
|
|
164
147
|
...style,
|
|
165
148
|
}, children: children }));
|
|
166
149
|
}
|
|
167
|
-
const MAX_POINTS = 20;
|
|
150
|
+
const MAX_POINTS = 20;
|
|
168
151
|
const generateToken = () => {
|
|
169
152
|
return crypto.randomUUID().replace(/-/g, '').substring(0, 16);
|
|
170
153
|
};
|
|
@@ -221,7 +204,7 @@ export default function DevPanel() {
|
|
|
221
204
|
const [rootEl, setRootEl] = useState(null);
|
|
222
205
|
const [doc, setDoc] = useState(null);
|
|
223
206
|
const [filters, setFilters] = useState({
|
|
224
|
-
Log: true, Info: true, Warn: true, Error: true, Debug: false
|
|
207
|
+
Log: true, Info: true, Warn: true, Error: true, Debug: false
|
|
225
208
|
});
|
|
226
209
|
const errorCount = logs.filter(l => l.type === 'Error').length;
|
|
227
210
|
const warnCount = logs.filter(l => l.type === 'Warn').length;
|
|
@@ -229,7 +212,6 @@ export default function DevPanel() {
|
|
|
229
212
|
const handleScroll = () => {
|
|
230
213
|
if (consoleScrollRef.current) {
|
|
231
214
|
const { scrollTop, scrollHeight, clientHeight } = consoleScrollRef.current;
|
|
232
|
-
// Buffer of 5px to handle zoom or rounding issues
|
|
233
215
|
const reachedBottom = scrollHeight - scrollTop - clientHeight < 5;
|
|
234
216
|
setIsAtBottom(reachedBottom);
|
|
235
217
|
}
|
|
@@ -239,230 +221,6 @@ export default function DevPanel() {
|
|
|
239
221
|
consoleScrollRef.current.scrollTop = consoleScrollRef.current.scrollHeight;
|
|
240
222
|
}
|
|
241
223
|
}, [filteredLogs, isAtBottom]);
|
|
242
|
-
/*useEffect(() => {
|
|
243
|
-
setRootEl(doc?.documentElement as HTMLElement);
|
|
244
|
-
}, []);
|
|
245
|
-
const [picking, setPicking] = React.useState(false);*/
|
|
246
|
-
/*const onMouseDown = (e: React.MouseEvent) => {
|
|
247
|
-
dragging || setDragging(true);
|
|
248
|
-
offset.current = { x: e.clientX - pos.x, y: e.clientY - pos.y };
|
|
249
|
-
e.preventDefault();
|
|
250
|
-
};
|
|
251
|
-
|
|
252
|
-
const onTouchStart = (e: React.TouchEvent) => {
|
|
253
|
-
const t = e.touches[0];
|
|
254
|
-
offset.current = { x: t.clientX - pos.x, y: t.clientY - pos.y };
|
|
255
|
-
};
|
|
256
|
-
|
|
257
|
-
const [rect, setRect] = React.useState<DOMRect | null>(null);
|
|
258
|
-
|
|
259
|
-
const [expandedPaths, setExpandedPaths] = useState<Set<string>>(new Set());
|
|
260
|
-
const [domVersion, setDomVersion] = useState(0);
|
|
261
|
-
|
|
262
|
-
const [currentNode, setCurrentNode] = useState<Element>(doc?.documentElement as HTMLElement);
|
|
263
|
-
const [history, setHistory] = useState<Element[]>([]);
|
|
264
|
-
|
|
265
|
-
const goInto = (el: Element) => {
|
|
266
|
-
setHistory(prev => [...prev, currentNode]);
|
|
267
|
-
setCurrentNode(el);
|
|
268
|
-
};
|
|
269
|
-
|
|
270
|
-
const goBack = () => {
|
|
271
|
-
setHistory(prev => {
|
|
272
|
-
const next = [...prev];
|
|
273
|
-
setCurrentNode(next.pop()!);
|
|
274
|
-
return next;
|
|
275
|
-
});
|
|
276
|
-
};
|
|
277
|
-
|
|
278
|
-
useEffect(() => {
|
|
279
|
-
const observer = new MutationObserver(() => setDomVersion(v => v + 1));
|
|
280
|
-
observer.observe(doc?.documentElement as HTMLElement, { childList: true, subtree: true, attributes: true });
|
|
281
|
-
return () => observer.disconnect();
|
|
282
|
-
}, []);
|
|
283
|
-
|
|
284
|
-
const getPath = (el: Element, depth: number) => el.tagName + depth + el.className + el.id;
|
|
285
|
-
|
|
286
|
-
const toggle = (path: string) => {
|
|
287
|
-
setExpandedPaths(prev => {
|
|
288
|
-
const next = new Set(prev);
|
|
289
|
-
next.has(path) ? next.delete(path) : next.add(path);
|
|
290
|
-
return next;
|
|
291
|
-
});
|
|
292
|
-
};
|
|
293
|
-
|
|
294
|
-
const colorizeJS = (code: string): string => {
|
|
295
|
-
const escaped = code.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
296
|
-
return escaped
|
|
297
|
-
.replace(/(\/\/[^\n]*)/g, '<span style="color:#888">$1</span>')
|
|
298
|
-
.replace(/(".*?"|'.*?')/g, '<span style="color:#0a0">$1</span>')
|
|
299
|
-
.replace(/\b(const|let|var|function|return|if|else|for|while|import|export|default|class|new)\b/g, '<span style="color:#e05">$1</span>')
|
|
300
|
-
.replace(/\b(\d+)\b/g, '<span style="color:#f90">$1</span>');
|
|
301
|
-
};
|
|
302
|
-
|
|
303
|
-
const colorizeCSS = (code: string): string => {
|
|
304
|
-
const escaped = code.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
305
|
-
return escaped
|
|
306
|
-
.replace(/([\w-]+)(\s*:)/g, '<span style="color:#f90">$1</span>$2')
|
|
307
|
-
.replace(/([.#]?[\w-]+)\s*\{/g, '<span style="color:#e05">$1</span> {');
|
|
308
|
-
};
|
|
309
|
-
|
|
310
|
-
const colorizeHtml = (code: string): React.ReactElement => {
|
|
311
|
-
const tokens: React.ReactElement[] = [];
|
|
312
|
-
let rest = code;
|
|
313
|
-
let i = 0;
|
|
314
|
-
|
|
315
|
-
const tagRegex = /(<\/?)([a-zA-Z][a-zA-Z0-9]*)([^>]*)(>)/g;
|
|
316
|
-
let last = 0;
|
|
317
|
-
let match;
|
|
318
|
-
|
|
319
|
-
while ((match = tagRegex.exec(code)) !== null) {
|
|
320
|
-
if (match.index > last)
|
|
321
|
-
tokens.push(<span key={i++} style={{ color: '#555' }}>{code.slice(last, match.index)}</span>);
|
|
322
|
-
tokens.push(<span key={i++} style={{ color: '#666' }}>{match[1]}</span>);
|
|
323
|
-
tokens.push(<span key={i++} style={{ color: '#e05' }}>{match[2]}</span>);
|
|
324
|
-
// colorize attributes
|
|
325
|
-
const attrStr = match[3];
|
|
326
|
-
const attrRegex = /(\s[a-zA-Z-]+)(="([^"]*)")?/g;
|
|
327
|
-
let am;
|
|
328
|
-
while ((am = attrRegex.exec(attrStr)) !== null) {
|
|
329
|
-
tokens.push(<span key={i++} style={{ color: '#f90' }}>{am[1]}</span>);
|
|
330
|
-
if (am[2]) {
|
|
331
|
-
tokens.push(<span key={i++} style={{ color: '#999' }}>="</span>);
|
|
332
|
-
tokens.push(<span key={i++} style={{ color: '#0a0' }}>{am[3]}</span>);
|
|
333
|
-
tokens.push(<span key={i++} style={{ color: '#999' }}>"</span>);
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
tokens.push(<span key={i++} style={{ color: '#666' }}>{match[4]}</span>);
|
|
337
|
-
last = match.index + match[0].length;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
if (last < code.length)
|
|
341
|
-
tokens.push(<span key={i++} style={{ color: '#555' }}>{code.slice(last)}</span>);
|
|
342
|
-
|
|
343
|
-
return <>{tokens}</>;
|
|
344
|
-
};
|
|
345
|
-
|
|
346
|
-
const renderNode = (el: Element, depth = 0, path = el.tagName): React.ReactElement => {
|
|
347
|
-
const isSelected = selected === el;
|
|
348
|
-
const tag = el.tagName.toLowerCase();
|
|
349
|
-
;const idAttr = el.id ? el.id : null;
|
|
350
|
-
const classAttr = el.className ? String(el.className).slice(0, 15) : null;
|
|
351
|
-
const otherAttrs = Array.from(el.attributes)
|
|
352
|
-
.filter(a => a.name !== 'id' && a.name !== 'class')
|
|
353
|
-
.slice(0, 2);
|
|
354
|
-
const text = Array.from(el.childNodes)
|
|
355
|
-
.filter(n => n.nodeType === Node.TEXT_NODE)
|
|
356
|
-
.map(n => n.textContent?.trim())
|
|
357
|
-
.filter(Boolean)
|
|
358
|
-
.join(' ');
|
|
359
|
-
|
|
360
|
-
const hasChildren = el.children.length > 0;
|
|
361
|
-
const hasText = text.length > 0;
|
|
362
|
-
const expandable = hasChildren || hasText;
|
|
363
|
-
const isExpanded = expandedPaths.has(path);*/
|
|
364
|
-
/*return (
|
|
365
|
-
<div key={path}>
|
|
366
|
-
{*/ /* opening tag */ /*}
|
|
367
|
-
<div
|
|
368
|
-
onClick={() => { setSelected(el); setRect(el.getBoundingClientRect()); if (expandable) toggle(path); }}
|
|
369
|
-
style={{ paddingLeft: depth * 10 + 4, cursor: 'pointer', background: isSelected ? '#e0edff' : 'transparent', borderRadius: 4, fontSize: 11, fontFamily: 'monospace', whiteSpace: 'nowrap' }}
|
|
370
|
-
>
|
|
371
|
-
<span style={{ color: '#999' }}>{expandable ? (isExpanded ? 'âž' : 'â¸') : '¡'} </span>
|
|
372
|
-
<span style={{ color: '#666' }}><</span>
|
|
373
|
-
<span style={{ color: '#e05' }}>{tag}</span>
|
|
374
|
-
{idAttr && <><span style={{ color: '#f90' }}> id</span><span style={{ color: '#999' }}>="</span><span style={{ color: '#0a0' }}>{idAttr}</span><span style={{ color: '#999' }}>"</span></>}
|
|
375
|
-
{classAttr && <><span style={{ color: '#f90' }}> class</span><span style={{ color: '#999' }}>="</span><span style={{ color: '#0a0' }}>{classAttr}</span><span style={{ color: '#999' }}>"</span></>}
|
|
376
|
-
{otherAttrs.map(a => (
|
|
377
|
-
<span key={a.name}>
|
|
378
|
-
<span style={{ color: '#f90' }}> {a.name}</span>
|
|
379
|
-
<span style={{ color: '#999' }}>="</span>
|
|
380
|
-
<span style={{ color: '#0a0' }}>{a.value.slice(0, 20)}</span>
|
|
381
|
-
<span style={{ color: '#999' }}>"</span>
|
|
382
|
-
</span>
|
|
383
|
-
))}
|
|
384
|
-
<span style={{ color: '#666' }}>></span>
|
|
385
|
-
{!isExpanded && hasText && <span style={{ color: '#333' }}>{text.slice(0, 30)}{text.length > 30 ? '...' : ''}</span>}
|
|
386
|
-
{!isExpanded && <span style={{ color: '#e05' }}></{tag}></span>}
|
|
387
|
-
</div>
|
|
388
|
-
|
|
389
|
-
{/* expanded content */ /*}
|
|
390
|
-
{isExpanded && (
|
|
391
|
-
<>
|
|
392
|
-
{hasText && (
|
|
393
|
-
<div
|
|
394
|
-
style={{ paddingLeft: (depth + 2) * 10 + 4, fontSize: 11, fontFamily: 'monospace', whiteSpace: 'pre-wrap', wordBreak: 'break-all' }}
|
|
395
|
-
dangerouslySetInnerHTML={{
|
|
396
|
-
__html: tag === 'script'
|
|
397
|
-
? colorizeJS(text)
|
|
398
|
-
: tag === 'style'
|
|
399
|
-
? colorizeCSS(text)
|
|
400
|
-
: `"${text}"`
|
|
401
|
-
}}
|
|
402
|
-
/>
|
|
403
|
-
)}
|
|
404
|
-
{hasChildren && Array.from(el.children).map((child, i) =>
|
|
405
|
-
renderNode(child, depth + 1, path + '>' + child.tagName + i)
|
|
406
|
-
)}
|
|
407
|
-
{/* closing tag only shows separately when expanded */ /*}
|
|
408
|
-
<div style={{ paddingLeft: depth * 10 + 4, fontSize: 11, fontFamily: 'monospace', color: '#e05' }}>
|
|
409
|
-
<span style={{ color: '#666' }}></</span>
|
|
410
|
-
<span style={{ color: '#e05' }}>{tag}</span>
|
|
411
|
-
<span style={{ color: '#666' }}>></span>
|
|
412
|
-
</div>
|
|
413
|
-
</>
|
|
414
|
-
)}
|
|
415
|
-
</div>
|
|
416
|
-
);
|
|
417
|
-
};
|
|
418
|
-
|
|
419
|
-
useEffect(() => {
|
|
420
|
-
if (!picking) return;
|
|
421
|
-
const onOver = (e: MouseEvent) => (e.target as HTMLElement).style.outline = '2px solid #e05';
|
|
422
|
-
const onOut = (e: MouseEvent) => (e.target as HTMLElement).style.outline = '';
|
|
423
|
-
const onClick = (e: MouseEvent) => {
|
|
424
|
-
e.preventDefault();
|
|
425
|
-
e.stopPropagation();
|
|
426
|
-
const el = e.target as HTMLElement;
|
|
427
|
-
el.style.outline = '';
|
|
428
|
-
setSelected(el);
|
|
429
|
-
setRect(el.getBoundingClientRect());
|
|
430
|
-
setPicking(false);
|
|
431
|
-
};
|
|
432
|
-
document.addEventListener('mouseover', onOver);
|
|
433
|
-
document.addEventListener('mouseout', onOut);
|
|
434
|
-
document.addEventListener('click', onClick, true);
|
|
435
|
-
return () => {
|
|
436
|
-
document.removeEventListener('mouseover', onOver);
|
|
437
|
-
document.removeEventListener('mouseout', onOut);
|
|
438
|
-
document.removeEventListener('click', onClick, true);
|
|
439
|
-
};
|
|
440
|
-
}, [picking]);
|
|
441
|
-
|
|
442
|
-
const styles = selected ? window.getComputedStyle(selected) : null;
|
|
443
|
-
const isFlex = styles?.display === 'flex';
|
|
444
|
-
const isGrid = styles?.display === 'grid';
|
|
445
|
-
const rotation = styles?.transform !== 'none' ? styles?.transform : null;
|
|
446
|
-
const styleKeys = ['color','background','fontSize','margin','padding','display','width','height','position','borderRadius'];
|
|
447
|
-
|
|
448
|
-
useEffect(() => {
|
|
449
|
-
const onMove = (e: MouseEvent | TouchEvent) => {
|
|
450
|
-
if (!dragging) return;
|
|
451
|
-
const point = 'touches' in e ? e.touches[0] : e;
|
|
452
|
-
setPos({ x: point.clientX - offset.current.x, y: point.clientY - offset.current.y });
|
|
453
|
-
};
|
|
454
|
-
const onUp = () => setDragging(false);
|
|
455
|
-
window.addEventListener('mousemove', onMove as any);
|
|
456
|
-
window.addEventListener('mouseup', onUp);
|
|
457
|
-
window.addEventListener('touchmove', onMove as any);
|
|
458
|
-
window.addEventListener('touchend', onUp);
|
|
459
|
-
return () => {
|
|
460
|
-
window.removeEventListener('mousemove', onMove as any);
|
|
461
|
-
window.removeEventListener('mouseup', onUp);
|
|
462
|
-
window.removeEventListener('touchmove', onMove as any);
|
|
463
|
-
window.removeEventListener('touchend', onUp);
|
|
464
|
-
};
|
|
465
|
-
}, [dragging]);*/
|
|
466
224
|
const loop = () => {
|
|
467
225
|
frameCount.current++;
|
|
468
226
|
requestAnimationFrame(loop);
|
|
@@ -606,18 +364,6 @@ useEffect(() => {
|
|
|
606
364
|
update();
|
|
607
365
|
}, 500);
|
|
608
366
|
}, 250);
|
|
609
|
-
/*setInterval(() => {
|
|
610
|
-
//setFps(Math.round(1000 / (performance.now() - (window as any).lastFrameTime)));
|
|
611
|
-
const now = performance.now();
|
|
612
|
-
const last = (window as any).lastFrameTime;
|
|
613
|
-
if (typeof last === 'number') {
|
|
614
|
-
const _ = now - last;
|
|
615
|
-
if (fpsSpanRef.current) {
|
|
616
|
-
fpsSpanRef.current.textContent = _ > 0 ? Math.round(1000 / _).toString() : '0';
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
(window as any).lastFrameTime = now;
|
|
620
|
-
}, 1000)*/
|
|
621
367
|
}, []);
|
|
622
368
|
const restart = async () => {
|
|
623
369
|
const resp = await fetch('/__authtics_host_apis__/restart', {
|
|
@@ -647,7 +393,6 @@ useEffect(() => {
|
|
|
647
393
|
clearInterval(intervalId);
|
|
648
394
|
};
|
|
649
395
|
}, []);
|
|
650
|
-
// Accumulated bytes per 2s tick
|
|
651
396
|
const pendingSent = useRef(0);
|
|
652
397
|
const pendingReceived = useRef(0);
|
|
653
398
|
const [sentHistory, setSentHistory] = useState(Array(MAX_POINTS).fill(0));
|
|
@@ -659,7 +404,6 @@ useEffect(() => {
|
|
|
659
404
|
: consoleOpen || networkOpen || routeOpen || webpageInfoOpen || clientInfoOpen || aboutHostOpen
|
|
660
405
|
? '320px'
|
|
661
406
|
: '320px';
|
|
662
|
-
// Ping every second
|
|
663
407
|
useEffect(() => {
|
|
664
408
|
const dat = localStorage.getItem('dat');
|
|
665
409
|
const interval = setInterval(async () => {
|
|
@@ -676,11 +420,10 @@ useEffect(() => {
|
|
|
676
420
|
setPing__(Date.now() - timeStart);
|
|
677
421
|
}
|
|
678
422
|
}
|
|
679
|
-
catch {
|
|
423
|
+
catch { }
|
|
680
424
|
}, 1000);
|
|
681
425
|
return () => clearInterval(interval);
|
|
682
426
|
}, []);
|
|
683
|
-
// Chart tick every 2 seconds â snapshot pending bytes then reset
|
|
684
427
|
useEffect(() => {
|
|
685
428
|
const interval = setInterval(() => {
|
|
686
429
|
const s = pendingSent.current;
|
|
@@ -698,7 +441,6 @@ useEffect(() => {
|
|
|
698
441
|
}, 1000);
|
|
699
442
|
return () => clearInterval(interval);
|
|
700
443
|
}, []);
|
|
701
|
-
// Console + network interceptors
|
|
702
444
|
useEffect(() => {
|
|
703
445
|
installConsoleInterceptor();
|
|
704
446
|
installNetworkInterceptor((f) => setNetworkFetches(prev => [...prev.slice(-49), f]), (f) => setNetworkFetches(prev => prev.map(p => p.id === f.id ? f : p)), (sent) => {
|
|
@@ -749,13 +491,6 @@ useEffect(() => {
|
|
|
749
491
|
setHostName(window.location.hostname);
|
|
750
492
|
setDoc(document);
|
|
751
493
|
setProtocol(window.location.protocol.replace(':', '').toUpperCase());
|
|
752
|
-
// Test logs
|
|
753
|
-
//setTimeout(() => console.error('DevPanel rendered.'), 2000);
|
|
754
|
-
//setTimeout(() => console.error('DevPanel rendered. 2'), 2000);
|
|
755
|
-
/*setTimeout(() => console.debug('DevPanel rendered.'), 2000);
|
|
756
|
-
setTimeout(() => console.info('DevPanel rendered.'), 2000);
|
|
757
|
-
setTimeout(() => console.log('DevPanel rendered.'), 2000);
|
|
758
|
-
setTimeout(() => console.warn('DevPanel rendered.'), 2000);*/
|
|
759
494
|
return () => window.removeEventListener('authtics:log', handler);
|
|
760
495
|
}, []);
|
|
761
496
|
const getOS = () => {
|
|
@@ -941,7 +676,7 @@ useEffect(() => {
|
|
|
941
676
|
userSelect: 'none',
|
|
942
677
|
}, children: [_jsx("input", { type: "checkbox", checked: filters[type], onChange: () => toggleFilter(type), style: { display: 'none' } }), type] }, type))) }), _jsx("div", { style: { overflowY: 'auto', maxHeight: '220px' }, ref: consoleScrollRef, onScroll: handleScroll, children: filteredLogs.length === 0
|
|
943
678
|
? _jsx("div", { style: { padding: '16px 12px', color: '#aaa', fontSize: '12px' }, children: "No logs yet." })
|
|
944
|
-
: [...filteredLogs]
|
|
679
|
+
: [...filteredLogs].map(log => {
|
|
945
680
|
const s = TYPE_STYLE[log.type];
|
|
946
681
|
return (_jsxs("div", { style: {
|
|
947
682
|
display: 'flex',
|
|
@@ -955,12 +690,7 @@ useEffect(() => {
|
|
|
955
690
|
fontSize: '11px',
|
|
956
691
|
fontFamily: 'monospace',
|
|
957
692
|
wordBreak: 'break-all',
|
|
958
|
-
}, children: log.
|
|
959
|
-
fontFamily: 'monospace',
|
|
960
|
-
fontSize: '12px',
|
|
961
|
-
whiteSpace: 'pre-wrap',
|
|
962
|
-
wordBreak: 'break-all',
|
|
963
|
-
}, dangerouslySetInnerHTML: { __html: Prism.highlight(log.message, Prism.languages.javascript, 'javascript') } })] })) : log.message })] }, log.id));
|
|
693
|
+
}, children: log.message })] }, log.id));
|
|
964
694
|
}) })] })) : networkOpen ? (_jsxs(_Fragment, { children: [_jsxs("div", { style: {
|
|
965
695
|
padding: '6px 8px',
|
|
966
696
|
borderBottom: '1px solid #ddd',
|
package/dist/renderEngine.js
CHANGED
|
@@ -1,36 +1,20 @@
|
|
|
1
|
-
// src/renderEngine.ts
|
|
2
1
|
import path from "node:path";
|
|
3
2
|
import fs from "node:fs";
|
|
4
3
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { createRequire } from "node:module";
|
|
6
4
|
import * as esbuild from "esbuild";
|
|
5
|
+
import * as babel from "@babel/core";
|
|
6
|
+
import * as babelPresetEnv from "@babel/preset-env";
|
|
7
7
|
import React from "react";
|
|
8
8
|
import { renderToString } from "react-dom/server";
|
|
9
9
|
import { tempFileForTsx, writeTempFile, readTempFile, ensureTempDirs, } from "./rendererTempFiles.js";
|
|
10
10
|
const __filename = fileURLToPath(import.meta.url);
|
|
11
11
|
const __dirname = path.dirname(__filename);
|
|
12
|
-
//const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
|
13
|
-
const require = createRequire(import.meta.url);
|
|
14
|
-
// ---------------------------------------------------------------------------
|
|
15
|
-
// Babel compiler (optional â only used if compilerType: 'babel' in config)
|
|
16
|
-
// Lazily required so babel doesn't need to be installed for esbuild users.
|
|
17
|
-
// ---------------------------------------------------------------------------
|
|
18
12
|
async function buildWithBabel(entryPath, virtualContent, platform) {
|
|
19
|
-
let babel;
|
|
20
|
-
try {
|
|
21
|
-
babel = require('@babel/core');
|
|
22
|
-
}
|
|
23
|
-
catch {
|
|
24
|
-
throw new Error('[authtics] compilerType is "babel" but @babel/core is not installed.\n' +
|
|
25
|
-
'Run: npm install --save-dev @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript');
|
|
26
|
-
}
|
|
27
13
|
const presets = [
|
|
28
|
-
[
|
|
14
|
+
[babelPresetEnv, {
|
|
29
15
|
targets: platform === 'node' ? { node: 'current' } : { browsers: '>0.5%, not dead' },
|
|
30
16
|
modules: platform === 'node' ? 'commonjs' : false,
|
|
31
17
|
}],
|
|
32
|
-
[require('@babel/preset-react'), { runtime: 'automatic' }],
|
|
33
|
-
[require('@babel/preset-typescript'), {}],
|
|
34
18
|
];
|
|
35
19
|
const result = await babel.transformAsync(virtualContent, {
|
|
36
20
|
filename: entryPath,
|
|
@@ -41,9 +25,6 @@ async function buildWithBabel(entryPath, virtualContent, platform) {
|
|
|
41
25
|
throw new Error('[authtics] Babel produced no output');
|
|
42
26
|
return result.code;
|
|
43
27
|
}
|
|
44
|
-
// ---------------------------------------------------------------------------
|
|
45
|
-
// esbuild compiler (default)
|
|
46
|
-
// ---------------------------------------------------------------------------
|
|
47
28
|
async function buildWithEsbuild(virtualContent, resolveDir, platform) {
|
|
48
29
|
const isNode = platform === 'node';
|
|
49
30
|
const result = await esbuild.build({
|
|
@@ -54,8 +35,6 @@ async function buildWithEsbuild(virtualContent, resolveDir, platform) {
|
|
|
54
35
|
},
|
|
55
36
|
bundle: true,
|
|
56
37
|
write: false,
|
|
57
|
-
// Server: CJS so require() works (e.g. lucide-react)
|
|
58
|
-
// Client: IIFE so it runs as a plain <script> tag
|
|
59
38
|
format: isNode ? 'cjs' : 'iife',
|
|
60
39
|
platform,
|
|
61
40
|
jsx: 'automatic',
|
|
@@ -63,8 +42,6 @@ async function buildWithEsbuild(virtualContent, resolveDir, platform) {
|
|
|
63
42
|
target: isNode ? ['node18'] : ['es2020'],
|
|
64
43
|
minify: false,
|
|
65
44
|
treeShaking: true,
|
|
66
|
-
// Server: externalize React to avoid duplicate instance â "Invalid hook call"
|
|
67
|
-
// Client: bundle React in so the browser needs no CDN or importmap
|
|
68
45
|
external: isNode
|
|
69
46
|
? ['react', 'react-dom', 'react-dom/server', 'react/jsx-runtime', 'react/jsx-dev-runtime', 'node:http',
|
|
70
47
|
'node:path',
|
|
@@ -78,9 +55,6 @@ async function buildWithEsbuild(virtualContent, resolveDir, platform) {
|
|
|
78
55
|
});
|
|
79
56
|
return result.outputFiles[0].text;
|
|
80
57
|
}
|
|
81
|
-
// ---------------------------------------------------------------------------
|
|
82
|
-
// Resolve devPanel.tsx
|
|
83
|
-
// ---------------------------------------------------------------------------
|
|
84
58
|
export function findFileUpwards(startDir, relativePath) {
|
|
85
59
|
let dir = path.resolve(startDir);
|
|
86
60
|
const root = path.parse(dir).root;
|
|
@@ -95,19 +69,12 @@ export function findFileUpwards(startDir, relativePath) {
|
|
|
95
69
|
return null;
|
|
96
70
|
}
|
|
97
71
|
function resolveDevPanelPath() {
|
|
98
|
-
/*const fromCwd = findFileUpwards(process.cwd(), path.join("src", "devPanel.tsx"));
|
|
99
|
-
if (fromCwd) return fromCwd;
|
|
100
|
-
const fromParent = findFileUpwards(path.join(process.cwd(), ".."), path.join("src", "devPanel.tsx"));
|
|
101
|
-
if (fromParent) return fromParent;*/
|
|
102
72
|
const nextToCli = path.join(__dirname, "devPanel.js");
|
|
103
73
|
if (fs.existsSync(nextToCli))
|
|
104
74
|
return nextToCli;
|
|
105
75
|
return null;
|
|
106
76
|
}
|
|
107
77
|
const devPanelPath = resolveDevPanelPath();
|
|
108
|
-
// ---------------------------------------------------------------------------
|
|
109
|
-
// Template resolution
|
|
110
|
-
// ---------------------------------------------------------------------------
|
|
111
78
|
function resolveTemplate(tsxPath) {
|
|
112
79
|
const pagesDir = path.join(process.cwd(), "pages");
|
|
113
80
|
let dir = path.dirname(tsxPath);
|
|
@@ -144,7 +111,6 @@ function buildHtml(opts) {
|
|
|
144
111
|
<head>
|
|
145
112
|
<meta charset="utf-8" />
|
|
146
113
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
147
|
-
<title>Authtics</title>
|
|
148
114
|
</head>
|
|
149
115
|
<body>
|
|
150
116
|
<div id="root">${pageHtml}</div>
|
|
@@ -154,21 +120,15 @@ function buildHtml(opts) {
|
|
|
154
120
|
</body>
|
|
155
121
|
</html>`;
|
|
156
122
|
}
|
|
157
|
-
// ---------------------------------------------------------------------------
|
|
158
|
-
// Cache validity
|
|
159
|
-
// ---------------------------------------------------------------------------
|
|
160
123
|
function isCacheValid(tsxPath, tempPath, serverCjsPath, templatePath, devPanelPath) {
|
|
161
124
|
if (!fs.existsSync(tempPath) || !fs.existsSync(serverCjsPath))
|
|
162
125
|
return false;
|
|
163
126
|
try {
|
|
164
127
|
const cacheMtime = fs.statSync(tempPath).mtimeMs;
|
|
165
|
-
// Page file changed?
|
|
166
128
|
if (fs.statSync(tsxPath).mtimeMs > cacheMtime)
|
|
167
129
|
return false;
|
|
168
|
-
// Template changed?
|
|
169
130
|
if (templatePath && fs.existsSync(templatePath) && fs.statSync(templatePath).mtimeMs > cacheMtime)
|
|
170
131
|
return false;
|
|
171
|
-
// DevPanel changed?
|
|
172
132
|
if (devPanelPath && fs.existsSync(devPanelPath) && fs.statSync(devPanelPath).mtimeMs > cacheMtime)
|
|
173
133
|
return false;
|
|
174
134
|
return true;
|
|
@@ -177,11 +137,6 @@ function isCacheValid(tsxPath, tempPath, serverCjsPath, templatePath, devPanelPa
|
|
|
177
137
|
return false;
|
|
178
138
|
}
|
|
179
139
|
}
|
|
180
|
-
// ---------------------------------------------------------------------------
|
|
181
|
-
// renderPage
|
|
182
|
-
// ---------------------------------------------------------------------------
|
|
183
|
-
// Small script injected into every page when hotReload is enabled.
|
|
184
|
-
// It opens an SSE connection to /__authtics_hmr__ and reloads on signal.
|
|
185
140
|
const HMR_SCRIPT = `
|
|
186
141
|
<script>
|
|
187
142
|
(function() {
|
|
@@ -198,10 +153,6 @@ export async function renderPage(tsxPath, reqUrl, showPanel = true, compiler = '
|
|
|
198
153
|
}
|
|
199
154
|
const usesDevPanel = showPanel && devPanelPath !== null;
|
|
200
155
|
const templatePath = resolveTemplate(tsxPath);
|
|
201
|
-
/*console.log(`[authtics] compiler = ${compiler}`);
|
|
202
|
-
console.log(`[authtics] devPanelPath = ${devPanelPath ?? "(not found)"}`);
|
|
203
|
-
console.log(`[authtics] usesDevPanel = ${usesDevPanel}`);
|
|
204
|
-
console.log(`[authtics] template = ${templatePath ?? "(none, using default)"}`);*/
|
|
205
156
|
const tempPath = tempFileForTsx(tsxPath);
|
|
206
157
|
const serverCjsPath = tempPath.replace(/\.json$/, ".server.cjs");
|
|
207
158
|
let clientCode;
|
|
@@ -211,14 +162,11 @@ export async function renderPage(tsxPath, reqUrl, showPanel = true, compiler = '
|
|
|
211
162
|
const parsed = JSON.parse(readTempFile(tempPath));
|
|
212
163
|
clientCode = parsed.clientCode;
|
|
213
164
|
serverCode = fs.readFileSync(serverCjsPath, "utf-8");
|
|
214
|
-
//console.log(`[authtics] using cached bundle for ${path.basename(tsxPath)}`);
|
|
215
165
|
}
|
|
216
166
|
catch {
|
|
217
|
-
// corrupt â rebuild
|
|
218
167
|
}
|
|
219
168
|
}
|
|
220
169
|
if (!clientCode || !serverCode) {
|
|
221
|
-
//console.log(`[authtics] building bundle (${compiler}) for ${path.basename(tsxPath)}...`);
|
|
222
170
|
const virtualServer = `
|
|
223
171
|
import Page from ${JSON.stringify(tsxPath)};
|
|
224
172
|
${usesDevPanel ? `import DevPanel from ${JSON.stringify(devPanelPath)};` : ""}
|
|
@@ -248,9 +196,7 @@ export async function renderPage(tsxPath, reqUrl, showPanel = true, compiler = '
|
|
|
248
196
|
}
|
|
249
197
|
fs.writeFileSync(serverCjsPath, serverCode, "utf-8");
|
|
250
198
|
writeTempFile(tempPath, JSON.stringify({ clientCode }));
|
|
251
|
-
//console.log(`[authtics] bundle cached for ${path.basename(tsxPath)}`);
|
|
252
199
|
}
|
|
253
|
-
// SSR
|
|
254
200
|
const serverModule = await import(`${serverCjsPath}?t=${Date.now()}`);
|
|
255
201
|
const exports = serverModule.default ?? serverModule;
|
|
256
202
|
const PageComponent = exports.default ?? exports.Page ?? exports.page ?? null;
|
|
@@ -1,19 +1,6 @@
|
|
|
1
1
|
export declare function ensureTempDirs(): void;
|
|
2
|
-
/**
|
|
3
|
-
* Returns the cache file path for a given source .tsx file.
|
|
4
|
-
* Uses the basename + a short hash of the full path to avoid collisions
|
|
5
|
-
* between files named the same thing in different directories.
|
|
6
|
-
*
|
|
7
|
-
* Example: /project/pages/about.tsx â .authtics_host/cache/about.tsx_a3f9b2.json
|
|
8
|
-
*/
|
|
9
2
|
export declare function tempFileForTsx(tsxPath: string): string;
|
|
10
|
-
/**
|
|
11
|
-
* Returns true if the cache file exists and is at least as new as the source file.
|
|
12
|
-
*/
|
|
13
3
|
export declare function isTempUpToDate(tsxPath: string, tempPath: string): boolean;
|
|
14
4
|
export declare function writeTempFile(tempPath: string, content: string): void;
|
|
15
5
|
export declare function readTempFile(tempPath: string): string;
|
|
16
|
-
/**
|
|
17
|
-
* Wipes the entire cache directory. Useful for a `authtics clean` CLI command.
|
|
18
|
-
*/
|
|
19
6
|
export declare function clearCache(): void;
|
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
// src/rendererTempFiles.ts
|
|
2
1
|
import fs from "node:fs";
|
|
3
2
|
import path from "node:path";
|
|
4
|
-
// ---------------------------------------------------------------------------
|
|
5
|
-
// Directory layout:
|
|
6
|
-
// <project>/.authtics_host/
|
|
7
|
-
// cache/ â compiled server+client bundles (JSON, keyed by source)
|
|
8
|
-
// ---------------------------------------------------------------------------
|
|
9
3
|
const AUTHTICS_DIR = path.join(process.cwd(), ".authtics_host");
|
|
10
4
|
const CACHE_DIR = path.join(AUTHTICS_DIR, "cache");
|
|
11
5
|
export function ensureTempDirs() {
|
|
@@ -14,22 +8,12 @@ export function ensureTempDirs() {
|
|
|
14
8
|
if (!fs.existsSync(CACHE_DIR))
|
|
15
9
|
fs.mkdirSync(CACHE_DIR, { recursive: true });
|
|
16
10
|
}
|
|
17
|
-
/**
|
|
18
|
-
* Returns the cache file path for a given source .tsx file.
|
|
19
|
-
* Uses the basename + a short hash of the full path to avoid collisions
|
|
20
|
-
* between files named the same thing in different directories.
|
|
21
|
-
*
|
|
22
|
-
* Example: /project/pages/about.tsx â .authtics_host/cache/about.tsx_a3f9b2.json
|
|
23
|
-
*/
|
|
24
11
|
export function tempFileForTsx(tsxPath) {
|
|
25
12
|
ensureTempDirs();
|
|
26
13
|
const base = path.basename(tsxPath);
|
|
27
14
|
const hash = simpleHash(tsxPath);
|
|
28
15
|
return path.join(CACHE_DIR, `${base}_${hash}.json`);
|
|
29
16
|
}
|
|
30
|
-
/**
|
|
31
|
-
* Returns true if the cache file exists and is at least as new as the source file.
|
|
32
|
-
*/
|
|
33
17
|
export function isTempUpToDate(tsxPath, tempPath) {
|
|
34
18
|
try {
|
|
35
19
|
const srcMtime = fs.statSync(tsxPath).mtimeMs;
|
|
@@ -46,19 +30,12 @@ export function writeTempFile(tempPath, content) {
|
|
|
46
30
|
export function readTempFile(tempPath) {
|
|
47
31
|
return fs.readFileSync(tempPath, "utf-8");
|
|
48
32
|
}
|
|
49
|
-
/**
|
|
50
|
-
* Wipes the entire cache directory. Useful for a `authtics clean` CLI command.
|
|
51
|
-
*/
|
|
52
33
|
export function clearCache() {
|
|
53
34
|
if (fs.existsSync(CACHE_DIR)) {
|
|
54
35
|
fs.rmSync(CACHE_DIR, { recursive: true, force: true });
|
|
55
36
|
}
|
|
56
37
|
ensureTempDirs();
|
|
57
38
|
}
|
|
58
|
-
// ---------------------------------------------------------------------------
|
|
59
|
-
// Internal helpers
|
|
60
|
-
// ---------------------------------------------------------------------------
|
|
61
|
-
/** Tiny non-cryptographic hash â good enough to avoid filename collisions. */
|
|
62
39
|
function simpleHash(str) {
|
|
63
40
|
let h = 0;
|
|
64
41
|
for (let i = 0; i < str.length; i++) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bananacool467/authtics-host",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2-beta.2",
|
|
4
4
|
"description": "A Next.js-inspired SSR framework for Node.js with TypeScript and JSX support.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -40,25 +40,27 @@
|
|
|
40
40
|
"author": "bananacool467",
|
|
41
41
|
"license": "MIT",
|
|
42
42
|
"dependencies": {
|
|
43
|
+
"@babel/core": "^7.29.0",
|
|
44
|
+
"@babel/preset-env": "^7.29.0",
|
|
45
|
+
"@babel/preset-react": "^7.28.5",
|
|
46
|
+
"@babel/preset-typescript": "^7.28.5",
|
|
47
|
+
"@types/babel__core": "^7.20.5",
|
|
48
|
+
"@types/babel__preset-env": "^7.10.0",
|
|
43
49
|
"esbuild": "^0.27.3",
|
|
44
50
|
"jiti": "^2.6.1",
|
|
45
51
|
"lucide-react": "^0.577.0",
|
|
46
52
|
"prismjs": "^1.30.0",
|
|
47
|
-
"react-device-detect": "^2.2.3"
|
|
48
|
-
"@babel/core": "^7.29.0",
|
|
49
|
-
"@babel/preset-env": "^7.29.0",
|
|
50
|
-
"@babel/preset-react": "^7.28.5",
|
|
51
|
-
"@babel/preset-typescript": "^7.28.5"
|
|
53
|
+
"react-device-detect": "^2.2.3"
|
|
52
54
|
},
|
|
53
55
|
"peerDependencies": {
|
|
54
56
|
"react": "^19.2.4",
|
|
55
57
|
"react-dom": "^19.2.4"
|
|
56
58
|
},
|
|
57
59
|
"devDependencies": {
|
|
60
|
+
"@types/node": "^22.13.11",
|
|
61
|
+
"@types/prismjs": "^1.26.6",
|
|
58
62
|
"@types/react": "^19.2.14",
|
|
59
63
|
"@types/react-dom": "^19.2.3",
|
|
60
|
-
"@types/react-syntax-highlighter": "^15.5.13"
|
|
61
|
-
"@types/node": "^22.13.11",
|
|
62
|
-
"@types/prismjs": "^1.26.6"
|
|
64
|
+
"@types/react-syntax-highlighter": "^15.5.13"
|
|
63
65
|
}
|
|
64
66
|
}
|