@bananacool467/authtics-host 0.0.1-beta.8 → 0.0.2-beta.1
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 -1
- package/dist/cli.js +24 -69
- package/dist/devPanel.js +32 -292
- package/dist/renderEngine.js +0 -43
- package/dist/rendererTempFiles.d.ts +0 -13
- package/dist/rendererTempFiles.js +0 -23
- package/package.json +1 -1
package/README.md
CHANGED
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,13 +92,29 @@ 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() {
|
|
112
98
|
this.server = start();
|
|
99
|
+
this.post('/__authtics_host_apis__/get-dat-restriction-missing', async (req, res) => {
|
|
100
|
+
const reqBody = (await getJsonBody(req));
|
|
101
|
+
let datMatched = false;
|
|
102
|
+
let datRestrictedEnv = false;
|
|
103
|
+
if (config_.dat && config_.dat == reqBody.dat)
|
|
104
|
+
datMatched = true;
|
|
105
|
+
if (config_.dat == undefined)
|
|
106
|
+
datMatched = true;
|
|
107
|
+
if (config_.dat && Array.isArray(config_.dat) && config_.dat.includes(reqBody.dat))
|
|
108
|
+
datMatched = true;
|
|
109
|
+
if (datMatched) {
|
|
110
|
+
if (config_.dat !== undefined)
|
|
111
|
+
datRestrictedEnv = true;
|
|
112
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
113
|
+
res.end(JSON.stringify({
|
|
114
|
+
enabled: datRestrictedEnv
|
|
115
|
+
}));
|
|
116
|
+
}
|
|
117
|
+
});
|
|
113
118
|
this.post('/__authtics_host_apis__/get-dat-matching', async (req, res) => {
|
|
114
119
|
const reqBody = (await getJsonBody(req));
|
|
115
120
|
let datMatched = false;
|
|
@@ -180,8 +185,6 @@ export default class App {
|
|
|
180
185
|
let datMatched = false;
|
|
181
186
|
if (config_.dat && config_.dat == reqBody.dat)
|
|
182
187
|
datMatched = true;
|
|
183
|
-
if (config_.dat == undefined)
|
|
184
|
-
datMatched = true;
|
|
185
188
|
if (config_.dat && Array.isArray(config_.dat) && config_.dat.includes(reqBody.dat))
|
|
186
189
|
datMatched = true;
|
|
187
190
|
if (datMatched) {
|
|
@@ -193,9 +196,7 @@ export default class App {
|
|
|
193
196
|
stdio: 'inherit'
|
|
194
197
|
});
|
|
195
198
|
child.unref();
|
|
196
|
-
//this.server.close(() => {
|
|
197
199
|
process.kill(process.pid, 'SIGTERM');
|
|
198
|
-
//})
|
|
199
200
|
}
|
|
200
201
|
});
|
|
201
202
|
this.post("/__authtics_host_apis__/shutdown", async (req, res) => {
|
|
@@ -203,8 +204,6 @@ export default class App {
|
|
|
203
204
|
let datMatched = false;
|
|
204
205
|
if (config_.dat && config_.dat == reqBody.dat)
|
|
205
206
|
datMatched = true;
|
|
206
|
-
if (config_.dat == undefined)
|
|
207
|
-
datMatched = true;
|
|
208
207
|
if (config_.dat && Array.isArray(config_.dat) && config_.dat.includes(reqBody.dat))
|
|
209
208
|
datMatched = true;
|
|
210
209
|
if (datMatched) {
|
|
@@ -314,22 +313,10 @@ export default class App {
|
|
|
314
313
|
if (config_.dat && Array.isArray(config_.dat) && config_.dat.includes(reqBody.dat))
|
|
315
314
|
datMatched = true;
|
|
316
315
|
if (datMatched) {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
res.end(JSON.stringify({
|
|
322
|
-
protocol,
|
|
323
|
-
cipher: cipher.name,
|
|
324
|
-
httpVersion: req.httpVersion
|
|
325
|
-
}));
|
|
326
|
-
}
|
|
327
|
-
else {
|
|
328
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
329
|
-
res.end(JSON.stringify({
|
|
330
|
-
httpVersion: req.httpVersion
|
|
331
|
-
}));
|
|
332
|
-
}
|
|
316
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
317
|
+
res.end(JSON.stringify({
|
|
318
|
+
httpVersion: req.httpVersion
|
|
319
|
+
}));
|
|
333
320
|
}
|
|
334
321
|
else {
|
|
335
322
|
const { url = "/", method } = req;
|
|
@@ -373,11 +360,7 @@ export default class App {
|
|
|
373
360
|
routes.set(`DELETE:${routePath}`, handler);
|
|
374
361
|
}
|
|
375
362
|
}
|
|
376
|
-
// ---------------------------------------------------------------------------
|
|
377
|
-
// Server
|
|
378
|
-
// ---------------------------------------------------------------------------
|
|
379
363
|
async function start() {
|
|
380
|
-
//console.log("🛠️ Initializing Authtics Host...");
|
|
381
364
|
let config = {};
|
|
382
365
|
try {
|
|
383
366
|
const configPath = path.join(process.cwd(), "authtics_host.config.ts");
|
|
@@ -385,7 +368,6 @@ async function start() {
|
|
|
385
368
|
config_ = config;
|
|
386
369
|
}
|
|
387
370
|
catch {
|
|
388
|
-
//console.log("ℹ️ No config found, using defaults.");
|
|
389
371
|
}
|
|
390
372
|
if (config.autoPage == null)
|
|
391
373
|
config.autoPage = true;
|
|
@@ -401,21 +383,15 @@ async function start() {
|
|
|
401
383
|
const publicDir = config.autoFile === true ? path.join(process.cwd(), config.autoFileDir || "") : undefined;
|
|
402
384
|
const apiDir = pagesDir ? path.join(pagesDir, "api") : undefined;
|
|
403
385
|
const server = createServer(async (req, res) => {
|
|
404
|
-
|
|
405
|
-
const actuallyShowPanel = showPanel; /* && (config.dat != undefined ? (body.dat == config.dat || (Array.isArray(config.dat) && config.dat.includes(body.dat))) : true)*/
|
|
386
|
+
const actuallyShowPanel = showPanel;
|
|
406
387
|
const r = augmentRes(res);
|
|
407
388
|
const { url = "/", method } = req;
|
|
408
|
-
// Strip query string, hash, trailing slash
|
|
409
389
|
let pathname = url.split("?")[0].split("#")[0];
|
|
410
390
|
if (pathname !== "/" && pathname.endsWith("/"))
|
|
411
391
|
pathname = pathname.slice(0, -1);
|
|
412
|
-
/*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") {
|
|
413
|
-
totalRequests++;
|
|
414
|
-
}*/
|
|
415
392
|
if (!pathname.includes('/__authtics_host_apis__/')) {
|
|
416
393
|
totalRequests++;
|
|
417
394
|
}
|
|
418
|
-
// ── 1. Registered routes (from app.get/post/etc.) ──────────────────────
|
|
419
395
|
const routeKey = `${method}:${pathname}`;
|
|
420
396
|
if (routes.has(routeKey)) {
|
|
421
397
|
const handler = routes.get(routeKey);
|
|
@@ -426,8 +402,6 @@ async function start() {
|
|
|
426
402
|
return handler(req, r);
|
|
427
403
|
}
|
|
428
404
|
}
|
|
429
|
-
// ── 2. Hot reload SSE endpoint ─────────────────────────────────────────
|
|
430
|
-
// Browser connects here and waits for a 'reload' event
|
|
431
405
|
if (pathname === '/__authtics_hmr__') {
|
|
432
406
|
res.writeHead(200, {
|
|
433
407
|
'Content-Type': 'text/event-stream',
|
|
@@ -441,11 +415,8 @@ async function start() {
|
|
|
441
415
|
req.on('close', () => { hmrClients.delete(res); users--; });
|
|
442
416
|
return;
|
|
443
417
|
}
|
|
444
|
-
// ── 3. Public folder — serves files from /public ────────────────────────
|
|
445
|
-
// e.g. GET /logo.png → public/logo.png
|
|
446
418
|
if (publicDir && fs.existsSync(publicDir)) {
|
|
447
419
|
const staticPath = path.join(publicDir, pathname);
|
|
448
|
-
// Make sure the resolved path is still inside publicDir (prevent traversal)
|
|
449
420
|
const safePath = path.resolve(staticPath);
|
|
450
421
|
if (safePath.startsWith(path.resolve(publicDir)) && fs.existsSync(safePath) && fs.statSync(safePath).isFile()) {
|
|
451
422
|
const ext = path.extname(safePath).toLowerCase();
|
|
@@ -454,15 +425,12 @@ async function start() {
|
|
|
454
425
|
return fs.createReadStream(safePath).pipe(res);
|
|
455
426
|
}
|
|
456
427
|
}
|
|
457
|
-
// ── 4. API routes — pages/api/*.ts files ────────────────────────────────
|
|
458
|
-
// e.g. GET /api/users → pages/api/users.ts
|
|
459
428
|
if (pathname.startsWith("/api/") && pagesDir != null) {
|
|
460
429
|
const apiFile = path.join(pagesDir, `${pathname}.ts`);
|
|
461
430
|
const apiFileTsx = path.join(pagesDir, `${pathname}.tsx`);
|
|
462
431
|
const resolvedApi = fs.existsSync(apiFile) ? apiFile : fs.existsSync(apiFileTsx) ? apiFileTsx : null;
|
|
463
432
|
if (resolvedApi) {
|
|
464
433
|
try {
|
|
465
|
-
// Use jiti to import the API route handler on the fly
|
|
466
434
|
const apiModule = await jiti.import(resolvedApi, { default: true });
|
|
467
435
|
const handler = apiModule.default ?? apiModule.handler ?? null;
|
|
468
436
|
if (typeof handler !== 'function') {
|
|
@@ -477,11 +445,9 @@ async function start() {
|
|
|
477
445
|
return res.end(JSON.stringify({ error: e.message }));
|
|
478
446
|
}
|
|
479
447
|
}
|
|
480
|
-
// /api/ path but no matching file → 404
|
|
481
448
|
res.writeHead(404);
|
|
482
449
|
return res.end(JSON.stringify({ error: `API route not found: ${pathname}` }));
|
|
483
450
|
}
|
|
484
|
-
// ── 5. TSX pages (SSR) ──────────────────────────────────────────────────
|
|
485
451
|
const urlPath = pathname === "/" ? "/index" : pathname;
|
|
486
452
|
const tsxPath = pagesDir ? path.join(pagesDir, `${urlPath}.tsx`) : undefined;
|
|
487
453
|
const htmlPath = pagesDir ? path.join(pagesDir, `${urlPath}.html`) : undefined;
|
|
@@ -497,12 +463,10 @@ async function start() {
|
|
|
497
463
|
return res.end(`<html><body><pre>500 | Render error:\n${e.message}</pre></body></html>`);
|
|
498
464
|
}
|
|
499
465
|
}
|
|
500
|
-
// ── 6. Static HTML pages ────────────────────────────────────────────────
|
|
501
466
|
if (htmlPath && fs.existsSync(htmlPath)) {
|
|
502
467
|
res.setHeader("Content-Type", "text/html");
|
|
503
468
|
return res.end(fs.readFileSync(htmlPath, "utf-8"));
|
|
504
469
|
}
|
|
505
|
-
// ── 7. App.tsx file handling ──────────────────────────────────────────────────────────────
|
|
506
470
|
if (routes.has("APP_TSX")) {
|
|
507
471
|
const appTsxPath = routes.get("APP_TSX");
|
|
508
472
|
const appPath = path.join(process.cwd(), appTsxPath);
|
|
@@ -519,7 +483,6 @@ async function start() {
|
|
|
519
483
|
}
|
|
520
484
|
}
|
|
521
485
|
}
|
|
522
|
-
// ── 8. 404 ──────────────────────────────────────────────────────────────
|
|
523
486
|
const notFoundPath = path.join(__dirname, '404.js');
|
|
524
487
|
if (fs.existsSync(notFoundPath)) {
|
|
525
488
|
try {
|
|
@@ -540,15 +503,12 @@ async function start() {
|
|
|
540
503
|
}
|
|
541
504
|
});
|
|
542
505
|
server.listen(PORT, () => {
|
|
543
|
-
//console.log(`🚀 Authtics Host live at http://localhost:${PORT}`);
|
|
544
506
|
setInterval(() => {
|
|
545
507
|
uptime++;
|
|
546
508
|
}, 1000);
|
|
547
509
|
});
|
|
548
|
-
// ── Hot reload file watcher ─────────────────────────────────────────────
|
|
549
510
|
if (config.hotReload === true) {
|
|
550
511
|
const watchDirs = [pagesDir];
|
|
551
|
-
// Also watch src/devPanel.tsx if it exists
|
|
552
512
|
const devPanelDir = path.join(__dirname, 'devPanel.js');
|
|
553
513
|
const _404Dir = path.join(__dirname, '404.js');
|
|
554
514
|
if (fs.existsSync(devPanelDir))
|
|
@@ -560,8 +520,6 @@ async function start() {
|
|
|
560
520
|
fs.watch(dir, { recursive: true }, (eventType, filename) => {
|
|
561
521
|
if (!filename)
|
|
562
522
|
return;
|
|
563
|
-
//console.log(`[authtics] file changed: ${filename}`);
|
|
564
|
-
// Clear the cache for any .tsx/.ts/.html file change
|
|
565
523
|
notifyHmrClients();
|
|
566
524
|
});
|
|
567
525
|
}
|
|
@@ -575,13 +533,10 @@ async function start() {
|
|
|
575
533
|
fs.watch(dir, { recursive: true }, (eventType, filename) => {
|
|
576
534
|
if (!filename)
|
|
577
535
|
return;
|
|
578
|
-
//console.log(`[authtics] file changed: ${filename}`);
|
|
579
|
-
// Clear the cache for any .tsx/.ts/.html file change
|
|
580
536
|
notifyHmrClients();
|
|
581
537
|
});
|
|
582
538
|
}
|
|
583
539
|
}
|
|
584
|
-
//console.log('🔥 Hot reload enabled — watching for file changes...');
|
|
585
540
|
}
|
|
586
541
|
return server;
|
|
587
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);
|
|
@@ -138,14 +121,14 @@ function installConsoleInterceptor() {
|
|
|
138
121
|
};
|
|
139
122
|
});
|
|
140
123
|
}
|
|
141
|
-
function PanelButton({ onClick, onClick2, children, style, fullWidth }) {
|
|
124
|
+
function PanelButton({ onClick, onClick2, children, style, fullWidth, disabled }) {
|
|
142
125
|
const [hovered, setHovered] = useState(false);
|
|
143
126
|
const [pressed, setPressed] = useState(false);
|
|
144
127
|
return (_jsx("div", { onClick: (e) => {
|
|
145
|
-
if (onClick) {
|
|
128
|
+
if (onClick && !disabled) {
|
|
146
129
|
onClick();
|
|
147
130
|
}
|
|
148
|
-
else if (onClick2) {
|
|
131
|
+
else if (onClick2 && !disabled) {
|
|
149
132
|
onClick2(e);
|
|
150
133
|
}
|
|
151
134
|
}, onMouseEnter: () => setHovered(true), onMouseLeave: () => { setHovered(false); setPressed(false); }, onMouseDown: () => setPressed(true), onMouseUp: () => setPressed(false), style: {
|
|
@@ -164,7 +147,7 @@ function PanelButton({ onClick, onClick2, children, style, fullWidth }) {
|
|
|
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
|
};
|
|
@@ -213,6 +196,7 @@ export default function DevPanel() {
|
|
|
213
196
|
const frames = useRef(0);
|
|
214
197
|
const consoleScrollRef = useRef(null);
|
|
215
198
|
const [isAtBottom, setIsAtBottom] = useState(true);
|
|
199
|
+
const [datRestrictionMissing, setDatRestrictionMissing] = useState(true);
|
|
216
200
|
const [pos, setPos] = React.useState({ x: 20, y: 20 });
|
|
217
201
|
const [dragging, setDragging] = React.useState(false);
|
|
218
202
|
const offset = React.useRef({ x: 0, y: 0 });
|
|
@@ -220,7 +204,7 @@ export default function DevPanel() {
|
|
|
220
204
|
const [rootEl, setRootEl] = useState(null);
|
|
221
205
|
const [doc, setDoc] = useState(null);
|
|
222
206
|
const [filters, setFilters] = useState({
|
|
223
|
-
Log: true, Info: true, Warn: true, Error: true, Debug: false
|
|
207
|
+
Log: true, Info: true, Warn: true, Error: true, Debug: false
|
|
224
208
|
});
|
|
225
209
|
const errorCount = logs.filter(l => l.type === 'Error').length;
|
|
226
210
|
const warnCount = logs.filter(l => l.type === 'Warn').length;
|
|
@@ -228,7 +212,6 @@ export default function DevPanel() {
|
|
|
228
212
|
const handleScroll = () => {
|
|
229
213
|
if (consoleScrollRef.current) {
|
|
230
214
|
const { scrollTop, scrollHeight, clientHeight } = consoleScrollRef.current;
|
|
231
|
-
// Buffer of 5px to handle zoom or rounding issues
|
|
232
215
|
const reachedBottom = scrollHeight - scrollTop - clientHeight < 5;
|
|
233
216
|
setIsAtBottom(reachedBottom);
|
|
234
217
|
}
|
|
@@ -238,230 +221,6 @@ export default function DevPanel() {
|
|
|
238
221
|
consoleScrollRef.current.scrollTop = consoleScrollRef.current.scrollHeight;
|
|
239
222
|
}
|
|
240
223
|
}, [filteredLogs, isAtBottom]);
|
|
241
|
-
/*useEffect(() => {
|
|
242
|
-
setRootEl(doc?.documentElement as HTMLElement);
|
|
243
|
-
}, []);
|
|
244
|
-
const [picking, setPicking] = React.useState(false);*/
|
|
245
|
-
/*const onMouseDown = (e: React.MouseEvent) => {
|
|
246
|
-
dragging || setDragging(true);
|
|
247
|
-
offset.current = { x: e.clientX - pos.x, y: e.clientY - pos.y };
|
|
248
|
-
e.preventDefault();
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
const onTouchStart = (e: React.TouchEvent) => {
|
|
252
|
-
const t = e.touches[0];
|
|
253
|
-
offset.current = { x: t.clientX - pos.x, y: t.clientY - pos.y };
|
|
254
|
-
};
|
|
255
|
-
|
|
256
|
-
const [rect, setRect] = React.useState<DOMRect | null>(null);
|
|
257
|
-
|
|
258
|
-
const [expandedPaths, setExpandedPaths] = useState<Set<string>>(new Set());
|
|
259
|
-
const [domVersion, setDomVersion] = useState(0);
|
|
260
|
-
|
|
261
|
-
const [currentNode, setCurrentNode] = useState<Element>(doc?.documentElement as HTMLElement);
|
|
262
|
-
const [history, setHistory] = useState<Element[]>([]);
|
|
263
|
-
|
|
264
|
-
const goInto = (el: Element) => {
|
|
265
|
-
setHistory(prev => [...prev, currentNode]);
|
|
266
|
-
setCurrentNode(el);
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
const goBack = () => {
|
|
270
|
-
setHistory(prev => {
|
|
271
|
-
const next = [...prev];
|
|
272
|
-
setCurrentNode(next.pop()!);
|
|
273
|
-
return next;
|
|
274
|
-
});
|
|
275
|
-
};
|
|
276
|
-
|
|
277
|
-
useEffect(() => {
|
|
278
|
-
const observer = new MutationObserver(() => setDomVersion(v => v + 1));
|
|
279
|
-
observer.observe(doc?.documentElement as HTMLElement, { childList: true, subtree: true, attributes: true });
|
|
280
|
-
return () => observer.disconnect();
|
|
281
|
-
}, []);
|
|
282
|
-
|
|
283
|
-
const getPath = (el: Element, depth: number) => el.tagName + depth + el.className + el.id;
|
|
284
|
-
|
|
285
|
-
const toggle = (path: string) => {
|
|
286
|
-
setExpandedPaths(prev => {
|
|
287
|
-
const next = new Set(prev);
|
|
288
|
-
next.has(path) ? next.delete(path) : next.add(path);
|
|
289
|
-
return next;
|
|
290
|
-
});
|
|
291
|
-
};
|
|
292
|
-
|
|
293
|
-
const colorizeJS = (code: string): string => {
|
|
294
|
-
const escaped = code.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
295
|
-
return escaped
|
|
296
|
-
.replace(/(\/\/[^\n]*)/g, '<span style="color:#888">$1</span>')
|
|
297
|
-
.replace(/(".*?"|'.*?')/g, '<span style="color:#0a0">$1</span>')
|
|
298
|
-
.replace(/\b(const|let|var|function|return|if|else|for|while|import|export|default|class|new)\b/g, '<span style="color:#e05">$1</span>')
|
|
299
|
-
.replace(/\b(\d+)\b/g, '<span style="color:#f90">$1</span>');
|
|
300
|
-
};
|
|
301
|
-
|
|
302
|
-
const colorizeCSS = (code: string): string => {
|
|
303
|
-
const escaped = code.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
304
|
-
return escaped
|
|
305
|
-
.replace(/([\w-]+)(\s*:)/g, '<span style="color:#f90">$1</span>$2')
|
|
306
|
-
.replace(/([.#]?[\w-]+)\s*\{/g, '<span style="color:#e05">$1</span> {');
|
|
307
|
-
};
|
|
308
|
-
|
|
309
|
-
const colorizeHtml = (code: string): React.ReactElement => {
|
|
310
|
-
const tokens: React.ReactElement[] = [];
|
|
311
|
-
let rest = code;
|
|
312
|
-
let i = 0;
|
|
313
|
-
|
|
314
|
-
const tagRegex = /(<\/?)([a-zA-Z][a-zA-Z0-9]*)([^>]*)(>)/g;
|
|
315
|
-
let last = 0;
|
|
316
|
-
let match;
|
|
317
|
-
|
|
318
|
-
while ((match = tagRegex.exec(code)) !== null) {
|
|
319
|
-
if (match.index > last)
|
|
320
|
-
tokens.push(<span key={i++} style={{ color: '#555' }}>{code.slice(last, match.index)}</span>);
|
|
321
|
-
tokens.push(<span key={i++} style={{ color: '#666' }}>{match[1]}</span>);
|
|
322
|
-
tokens.push(<span key={i++} style={{ color: '#e05' }}>{match[2]}</span>);
|
|
323
|
-
// colorize attributes
|
|
324
|
-
const attrStr = match[3];
|
|
325
|
-
const attrRegex = /(\s[a-zA-Z-]+)(="([^"]*)")?/g;
|
|
326
|
-
let am;
|
|
327
|
-
while ((am = attrRegex.exec(attrStr)) !== null) {
|
|
328
|
-
tokens.push(<span key={i++} style={{ color: '#f90' }}>{am[1]}</span>);
|
|
329
|
-
if (am[2]) {
|
|
330
|
-
tokens.push(<span key={i++} style={{ color: '#999' }}>="</span>);
|
|
331
|
-
tokens.push(<span key={i++} style={{ color: '#0a0' }}>{am[3]}</span>);
|
|
332
|
-
tokens.push(<span key={i++} style={{ color: '#999' }}>"</span>);
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
tokens.push(<span key={i++} style={{ color: '#666' }}>{match[4]}</span>);
|
|
336
|
-
last = match.index + match[0].length;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
if (last < code.length)
|
|
340
|
-
tokens.push(<span key={i++} style={{ color: '#555' }}>{code.slice(last)}</span>);
|
|
341
|
-
|
|
342
|
-
return <>{tokens}</>;
|
|
343
|
-
};
|
|
344
|
-
|
|
345
|
-
const renderNode = (el: Element, depth = 0, path = el.tagName): React.ReactElement => {
|
|
346
|
-
const isSelected = selected === el;
|
|
347
|
-
const tag = el.tagName.toLowerCase();
|
|
348
|
-
;const idAttr = el.id ? el.id : null;
|
|
349
|
-
const classAttr = el.className ? String(el.className).slice(0, 15) : null;
|
|
350
|
-
const otherAttrs = Array.from(el.attributes)
|
|
351
|
-
.filter(a => a.name !== 'id' && a.name !== 'class')
|
|
352
|
-
.slice(0, 2);
|
|
353
|
-
const text = Array.from(el.childNodes)
|
|
354
|
-
.filter(n => n.nodeType === Node.TEXT_NODE)
|
|
355
|
-
.map(n => n.textContent?.trim())
|
|
356
|
-
.filter(Boolean)
|
|
357
|
-
.join(' ');
|
|
358
|
-
|
|
359
|
-
const hasChildren = el.children.length > 0;
|
|
360
|
-
const hasText = text.length > 0;
|
|
361
|
-
const expandable = hasChildren || hasText;
|
|
362
|
-
const isExpanded = expandedPaths.has(path);*/
|
|
363
|
-
/*return (
|
|
364
|
-
<div key={path}>
|
|
365
|
-
{*/ /* opening tag */ /*}
|
|
366
|
-
<div
|
|
367
|
-
onClick={() => { setSelected(el); setRect(el.getBoundingClientRect()); if (expandable) toggle(path); }}
|
|
368
|
-
style={{ paddingLeft: depth * 10 + 4, cursor: 'pointer', background: isSelected ? '#e0edff' : 'transparent', borderRadius: 4, fontSize: 11, fontFamily: 'monospace', whiteSpace: 'nowrap' }}
|
|
369
|
-
>
|
|
370
|
-
<span style={{ color: '#999' }}>{expandable ? (isExpanded ? '▾' : '▸') : '·'} </span>
|
|
371
|
-
<span style={{ color: '#666' }}><</span>
|
|
372
|
-
<span style={{ color: '#e05' }}>{tag}</span>
|
|
373
|
-
{idAttr && <><span style={{ color: '#f90' }}> id</span><span style={{ color: '#999' }}>="</span><span style={{ color: '#0a0' }}>{idAttr}</span><span style={{ color: '#999' }}>"</span></>}
|
|
374
|
-
{classAttr && <><span style={{ color: '#f90' }}> class</span><span style={{ color: '#999' }}>="</span><span style={{ color: '#0a0' }}>{classAttr}</span><span style={{ color: '#999' }}>"</span></>}
|
|
375
|
-
{otherAttrs.map(a => (
|
|
376
|
-
<span key={a.name}>
|
|
377
|
-
<span style={{ color: '#f90' }}> {a.name}</span>
|
|
378
|
-
<span style={{ color: '#999' }}>="</span>
|
|
379
|
-
<span style={{ color: '#0a0' }}>{a.value.slice(0, 20)}</span>
|
|
380
|
-
<span style={{ color: '#999' }}>"</span>
|
|
381
|
-
</span>
|
|
382
|
-
))}
|
|
383
|
-
<span style={{ color: '#666' }}>></span>
|
|
384
|
-
{!isExpanded && hasText && <span style={{ color: '#333' }}>{text.slice(0, 30)}{text.length > 30 ? '...' : ''}</span>}
|
|
385
|
-
{!isExpanded && <span style={{ color: '#e05' }}></{tag}></span>}
|
|
386
|
-
</div>
|
|
387
|
-
|
|
388
|
-
{/* expanded content */ /*}
|
|
389
|
-
{isExpanded && (
|
|
390
|
-
<>
|
|
391
|
-
{hasText && (
|
|
392
|
-
<div
|
|
393
|
-
style={{ paddingLeft: (depth + 2) * 10 + 4, fontSize: 11, fontFamily: 'monospace', whiteSpace: 'pre-wrap', wordBreak: 'break-all' }}
|
|
394
|
-
dangerouslySetInnerHTML={{
|
|
395
|
-
__html: tag === 'script'
|
|
396
|
-
? colorizeJS(text)
|
|
397
|
-
: tag === 'style'
|
|
398
|
-
? colorizeCSS(text)
|
|
399
|
-
: `"${text}"`
|
|
400
|
-
}}
|
|
401
|
-
/>
|
|
402
|
-
)}
|
|
403
|
-
{hasChildren && Array.from(el.children).map((child, i) =>
|
|
404
|
-
renderNode(child, depth + 1, path + '>' + child.tagName + i)
|
|
405
|
-
)}
|
|
406
|
-
{/* closing tag only shows separately when expanded */ /*}
|
|
407
|
-
<div style={{ paddingLeft: depth * 10 + 4, fontSize: 11, fontFamily: 'monospace', color: '#e05' }}>
|
|
408
|
-
<span style={{ color: '#666' }}></</span>
|
|
409
|
-
<span style={{ color: '#e05' }}>{tag}</span>
|
|
410
|
-
<span style={{ color: '#666' }}>></span>
|
|
411
|
-
</div>
|
|
412
|
-
</>
|
|
413
|
-
)}
|
|
414
|
-
</div>
|
|
415
|
-
);
|
|
416
|
-
};
|
|
417
|
-
|
|
418
|
-
useEffect(() => {
|
|
419
|
-
if (!picking) return;
|
|
420
|
-
const onOver = (e: MouseEvent) => (e.target as HTMLElement).style.outline = '2px solid #e05';
|
|
421
|
-
const onOut = (e: MouseEvent) => (e.target as HTMLElement).style.outline = '';
|
|
422
|
-
const onClick = (e: MouseEvent) => {
|
|
423
|
-
e.preventDefault();
|
|
424
|
-
e.stopPropagation();
|
|
425
|
-
const el = e.target as HTMLElement;
|
|
426
|
-
el.style.outline = '';
|
|
427
|
-
setSelected(el);
|
|
428
|
-
setRect(el.getBoundingClientRect());
|
|
429
|
-
setPicking(false);
|
|
430
|
-
};
|
|
431
|
-
document.addEventListener('mouseover', onOver);
|
|
432
|
-
document.addEventListener('mouseout', onOut);
|
|
433
|
-
document.addEventListener('click', onClick, true);
|
|
434
|
-
return () => {
|
|
435
|
-
document.removeEventListener('mouseover', onOver);
|
|
436
|
-
document.removeEventListener('mouseout', onOut);
|
|
437
|
-
document.removeEventListener('click', onClick, true);
|
|
438
|
-
};
|
|
439
|
-
}, [picking]);
|
|
440
|
-
|
|
441
|
-
const styles = selected ? window.getComputedStyle(selected) : null;
|
|
442
|
-
const isFlex = styles?.display === 'flex';
|
|
443
|
-
const isGrid = styles?.display === 'grid';
|
|
444
|
-
const rotation = styles?.transform !== 'none' ? styles?.transform : null;
|
|
445
|
-
const styleKeys = ['color','background','fontSize','margin','padding','display','width','height','position','borderRadius'];
|
|
446
|
-
|
|
447
|
-
useEffect(() => {
|
|
448
|
-
const onMove = (e: MouseEvent | TouchEvent) => {
|
|
449
|
-
if (!dragging) return;
|
|
450
|
-
const point = 'touches' in e ? e.touches[0] : e;
|
|
451
|
-
setPos({ x: point.clientX - offset.current.x, y: point.clientY - offset.current.y });
|
|
452
|
-
};
|
|
453
|
-
const onUp = () => setDragging(false);
|
|
454
|
-
window.addEventListener('mousemove', onMove as any);
|
|
455
|
-
window.addEventListener('mouseup', onUp);
|
|
456
|
-
window.addEventListener('touchmove', onMove as any);
|
|
457
|
-
window.addEventListener('touchend', onUp);
|
|
458
|
-
return () => {
|
|
459
|
-
window.removeEventListener('mousemove', onMove as any);
|
|
460
|
-
window.removeEventListener('mouseup', onUp);
|
|
461
|
-
window.removeEventListener('touchmove', onMove as any);
|
|
462
|
-
window.removeEventListener('touchend', onUp);
|
|
463
|
-
};
|
|
464
|
-
}, [dragging]);*/
|
|
465
224
|
const loop = () => {
|
|
466
225
|
frameCount.current++;
|
|
467
226
|
requestAnimationFrame(loop);
|
|
@@ -489,6 +248,16 @@ useEffect(() => {
|
|
|
489
248
|
setDatMatch(data.isMatching);
|
|
490
249
|
if (!data.isMatching) {
|
|
491
250
|
doc?.getElementById('authtics-dev-panel-root')?.remove();
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
const resp2 = await fetch('/__authtics_host_apis__/get-dat-restriction-missing', {
|
|
254
|
+
method: 'POST',
|
|
255
|
+
headers: { 'Content-Type': 'application/json' },
|
|
256
|
+
body: JSON.stringify({ dat: localStorage.getItem('dat') })
|
|
257
|
+
});
|
|
258
|
+
if (resp2.status === 200) {
|
|
259
|
+
const data = await resp2.json();
|
|
260
|
+
setDatRestrictionMissing(!data.enabled);
|
|
492
261
|
}
|
|
493
262
|
}
|
|
494
263
|
};
|
|
@@ -595,18 +364,6 @@ useEffect(() => {
|
|
|
595
364
|
update();
|
|
596
365
|
}, 500);
|
|
597
366
|
}, 250);
|
|
598
|
-
/*setInterval(() => {
|
|
599
|
-
//setFps(Math.round(1000 / (performance.now() - (window as any).lastFrameTime)));
|
|
600
|
-
const now = performance.now();
|
|
601
|
-
const last = (window as any).lastFrameTime;
|
|
602
|
-
if (typeof last === 'number') {
|
|
603
|
-
const _ = now - last;
|
|
604
|
-
if (fpsSpanRef.current) {
|
|
605
|
-
fpsSpanRef.current.textContent = _ > 0 ? Math.round(1000 / _).toString() : '0';
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
(window as any).lastFrameTime = now;
|
|
609
|
-
}, 1000)*/
|
|
610
367
|
}, []);
|
|
611
368
|
const restart = async () => {
|
|
612
369
|
const resp = await fetch('/__authtics_host_apis__/restart', {
|
|
@@ -636,7 +393,6 @@ useEffect(() => {
|
|
|
636
393
|
clearInterval(intervalId);
|
|
637
394
|
};
|
|
638
395
|
}, []);
|
|
639
|
-
// Accumulated bytes per 2s tick
|
|
640
396
|
const pendingSent = useRef(0);
|
|
641
397
|
const pendingReceived = useRef(0);
|
|
642
398
|
const [sentHistory, setSentHistory] = useState(Array(MAX_POINTS).fill(0));
|
|
@@ -648,7 +404,6 @@ useEffect(() => {
|
|
|
648
404
|
: consoleOpen || networkOpen || routeOpen || webpageInfoOpen || clientInfoOpen || aboutHostOpen
|
|
649
405
|
? '320px'
|
|
650
406
|
: '320px';
|
|
651
|
-
// Ping every second
|
|
652
407
|
useEffect(() => {
|
|
653
408
|
const dat = localStorage.getItem('dat');
|
|
654
409
|
const interval = setInterval(async () => {
|
|
@@ -665,23 +420,11 @@ useEffect(() => {
|
|
|
665
420
|
setPing__(Date.now() - timeStart);
|
|
666
421
|
}
|
|
667
422
|
}
|
|
668
|
-
catch {
|
|
423
|
+
catch { }
|
|
669
424
|
}, 1000);
|
|
670
425
|
return () => clearInterval(interval);
|
|
671
426
|
}, []);
|
|
672
|
-
// Chart tick every 2 seconds — snapshot pending bytes then reset
|
|
673
427
|
useEffect(() => {
|
|
674
|
-
/*let nE;
|
|
675
|
-
const navigationEntries = performance.getEntriesByType("navigation");
|
|
676
|
-
if (navigationEntries.length > 0) {
|
|
677
|
-
const navigationEntry = navigationEntries[0] as PerformanceNavigationTiming;
|
|
678
|
-
nE = navigationEntry;
|
|
679
|
-
}
|
|
680
|
-
let statusCode = 404;
|
|
681
|
-
if (nE instanceof PerformanceNavigationTiming) {
|
|
682
|
-
statusCode = nE.responseStatus;
|
|
683
|
-
}
|
|
684
|
-
setStatus(statusCode);*/
|
|
685
428
|
const interval = setInterval(() => {
|
|
686
429
|
const s = pendingSent.current;
|
|
687
430
|
const r = pendingReceived.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',
|
|
@@ -1215,7 +945,17 @@ useEffect(() => {
|
|
|
1215
945
|
gap: '6px',
|
|
1216
946
|
color: '#fff',
|
|
1217
947
|
flexDirection: 'row',
|
|
1218
|
-
}, children: [_jsx(CircleX, { size: 12 }), "Invalid Developer Access Token (DAT)"] }) })) : (_jsx(_Fragment, {})),
|
|
948
|
+
}, children: [_jsx(CircleX, { size: 12 }), "Invalid Developer Access Token (DAT)"] }) })) : (_jsx(_Fragment, {})), hostVersion === "0.0.1-beta.9" ? (_jsx(_Fragment, { children: _jsx("div", { style: {
|
|
949
|
+
display: 'flex',
|
|
950
|
+
background: 'orange',
|
|
951
|
+
borderRadius: '10px',
|
|
952
|
+
padding: '6px 10px',
|
|
953
|
+
justifyContent: 'left',
|
|
954
|
+
alignItems: 'center',
|
|
955
|
+
gap: '6px',
|
|
956
|
+
color: '#fff',
|
|
957
|
+
flexDirection: 'row',
|
|
958
|
+
}, children: "Updates to security. Apps with version 0.0.1-beta.9 and over will now require DAT restriction to access remote features like restarting and shutting down the app." }) })) : (_jsx(_Fragment, {})), _jsx(PanelButton, { onClick: () => { restart(); }, fullWidth: true, style: { color: '#fff', justifyContent: 'center', height: '32px', background: '#ba4e4e' }, disabled: datRestrictionMissing, children: _jsx("span", { style: { display: 'flex', alignItems: 'center', gap: '6px', textAlign: 'center' }, children: "Restart App" }) }), datRestrictionMissing ? (_jsxs("span", { style: { display: 'flex', opacity: 0.5, fontSize: 10, justifyContent: 'center', alignItems: 'center', gap: '4px' }, children: [_jsx(Info, { size: 10 }), "Restarting the app is unavaliable. Set \"dat\" in the Authtics Host config to set restriction."] })) : (_jsx(_Fragment, {})), _jsx(PanelButton, { onClick: () => { shutDown(); }, fullWidth: true, style: { color: '#fff', justifyContent: 'center', height: '32px', background: '#ba4e4e' }, disabled: datRestrictionMissing, children: _jsx("span", { style: { display: 'flex', alignItems: 'center', gap: '6px', textAlign: 'center' }, children: "Shut Down App" }) }), datRestrictionMissing ? (_jsxs("span", { style: { display: 'flex', opacity: 0.5, fontSize: 10, justifyContent: 'center', alignItems: 'center', gap: '4px' }, children: [_jsx(Info, { size: 10 }), "Shutting down the app is unavaliable. Set \"dat\" in the Authtics Host config to set restriction."] })) : (_jsx(_Fragment, {}))] })] })) }) }), datMatch ? (_jsxs("div", { onClick: () => setOpen(o => !o), style: {
|
|
1219
959
|
display: 'flex',
|
|
1220
960
|
flexDirection: 'row',
|
|
1221
961
|
alignItems: 'center',
|
package/dist/renderEngine.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
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";
|
|
@@ -9,12 +8,7 @@ import { renderToString } from "react-dom/server";
|
|
|
9
8
|
import { tempFileForTsx, writeTempFile, readTempFile, ensureTempDirs, } from "./rendererTempFiles.js";
|
|
10
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
11
10
|
const __dirname = path.dirname(__filename);
|
|
12
|
-
//const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
|
13
11
|
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
13
|
let babel;
|
|
20
14
|
try {
|
|
@@ -41,9 +35,6 @@ async function buildWithBabel(entryPath, virtualContent, platform) {
|
|
|
41
35
|
throw new Error('[authtics] Babel produced no output');
|
|
42
36
|
return result.code;
|
|
43
37
|
}
|
|
44
|
-
// ---------------------------------------------------------------------------
|
|
45
|
-
// esbuild compiler (default)
|
|
46
|
-
// ---------------------------------------------------------------------------
|
|
47
38
|
async function buildWithEsbuild(virtualContent, resolveDir, platform) {
|
|
48
39
|
const isNode = platform === 'node';
|
|
49
40
|
const result = await esbuild.build({
|
|
@@ -54,8 +45,6 @@ async function buildWithEsbuild(virtualContent, resolveDir, platform) {
|
|
|
54
45
|
},
|
|
55
46
|
bundle: true,
|
|
56
47
|
write: false,
|
|
57
|
-
// Server: CJS so require() works (e.g. lucide-react)
|
|
58
|
-
// Client: IIFE so it runs as a plain <script> tag
|
|
59
48
|
format: isNode ? 'cjs' : 'iife',
|
|
60
49
|
platform,
|
|
61
50
|
jsx: 'automatic',
|
|
@@ -63,8 +52,6 @@ async function buildWithEsbuild(virtualContent, resolveDir, platform) {
|
|
|
63
52
|
target: isNode ? ['node18'] : ['es2020'],
|
|
64
53
|
minify: false,
|
|
65
54
|
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
55
|
external: isNode
|
|
69
56
|
? ['react', 'react-dom', 'react-dom/server', 'react/jsx-runtime', 'react/jsx-dev-runtime', 'node:http',
|
|
70
57
|
'node:path',
|
|
@@ -78,9 +65,6 @@ async function buildWithEsbuild(virtualContent, resolveDir, platform) {
|
|
|
78
65
|
});
|
|
79
66
|
return result.outputFiles[0].text;
|
|
80
67
|
}
|
|
81
|
-
// ---------------------------------------------------------------------------
|
|
82
|
-
// Resolve devPanel.tsx
|
|
83
|
-
// ---------------------------------------------------------------------------
|
|
84
68
|
export function findFileUpwards(startDir, relativePath) {
|
|
85
69
|
let dir = path.resolve(startDir);
|
|
86
70
|
const root = path.parse(dir).root;
|
|
@@ -95,19 +79,12 @@ export function findFileUpwards(startDir, relativePath) {
|
|
|
95
79
|
return null;
|
|
96
80
|
}
|
|
97
81
|
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
82
|
const nextToCli = path.join(__dirname, "devPanel.js");
|
|
103
83
|
if (fs.existsSync(nextToCli))
|
|
104
84
|
return nextToCli;
|
|
105
85
|
return null;
|
|
106
86
|
}
|
|
107
87
|
const devPanelPath = resolveDevPanelPath();
|
|
108
|
-
// ---------------------------------------------------------------------------
|
|
109
|
-
// Template resolution
|
|
110
|
-
// ---------------------------------------------------------------------------
|
|
111
88
|
function resolveTemplate(tsxPath) {
|
|
112
89
|
const pagesDir = path.join(process.cwd(), "pages");
|
|
113
90
|
let dir = path.dirname(tsxPath);
|
|
@@ -154,21 +131,15 @@ function buildHtml(opts) {
|
|
|
154
131
|
</body>
|
|
155
132
|
</html>`;
|
|
156
133
|
}
|
|
157
|
-
// ---------------------------------------------------------------------------
|
|
158
|
-
// Cache validity
|
|
159
|
-
// ---------------------------------------------------------------------------
|
|
160
134
|
function isCacheValid(tsxPath, tempPath, serverCjsPath, templatePath, devPanelPath) {
|
|
161
135
|
if (!fs.existsSync(tempPath) || !fs.existsSync(serverCjsPath))
|
|
162
136
|
return false;
|
|
163
137
|
try {
|
|
164
138
|
const cacheMtime = fs.statSync(tempPath).mtimeMs;
|
|
165
|
-
// Page file changed?
|
|
166
139
|
if (fs.statSync(tsxPath).mtimeMs > cacheMtime)
|
|
167
140
|
return false;
|
|
168
|
-
// Template changed?
|
|
169
141
|
if (templatePath && fs.existsSync(templatePath) && fs.statSync(templatePath).mtimeMs > cacheMtime)
|
|
170
142
|
return false;
|
|
171
|
-
// DevPanel changed?
|
|
172
143
|
if (devPanelPath && fs.existsSync(devPanelPath) && fs.statSync(devPanelPath).mtimeMs > cacheMtime)
|
|
173
144
|
return false;
|
|
174
145
|
return true;
|
|
@@ -177,11 +148,6 @@ function isCacheValid(tsxPath, tempPath, serverCjsPath, templatePath, devPanelPa
|
|
|
177
148
|
return false;
|
|
178
149
|
}
|
|
179
150
|
}
|
|
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
151
|
const HMR_SCRIPT = `
|
|
186
152
|
<script>
|
|
187
153
|
(function() {
|
|
@@ -198,10 +164,6 @@ export async function renderPage(tsxPath, reqUrl, showPanel = true, compiler = '
|
|
|
198
164
|
}
|
|
199
165
|
const usesDevPanel = showPanel && devPanelPath !== null;
|
|
200
166
|
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
167
|
const tempPath = tempFileForTsx(tsxPath);
|
|
206
168
|
const serverCjsPath = tempPath.replace(/\.json$/, ".server.cjs");
|
|
207
169
|
let clientCode;
|
|
@@ -211,14 +173,11 @@ export async function renderPage(tsxPath, reqUrl, showPanel = true, compiler = '
|
|
|
211
173
|
const parsed = JSON.parse(readTempFile(tempPath));
|
|
212
174
|
clientCode = parsed.clientCode;
|
|
213
175
|
serverCode = fs.readFileSync(serverCjsPath, "utf-8");
|
|
214
|
-
//console.log(`[authtics] using cached bundle for ${path.basename(tsxPath)}`);
|
|
215
176
|
}
|
|
216
177
|
catch {
|
|
217
|
-
// corrupt — rebuild
|
|
218
178
|
}
|
|
219
179
|
}
|
|
220
180
|
if (!clientCode || !serverCode) {
|
|
221
|
-
//console.log(`[authtics] building bundle (${compiler}) for ${path.basename(tsxPath)}...`);
|
|
222
181
|
const virtualServer = `
|
|
223
182
|
import Page from ${JSON.stringify(tsxPath)};
|
|
224
183
|
${usesDevPanel ? `import DevPanel from ${JSON.stringify(devPanelPath)};` : ""}
|
|
@@ -248,9 +207,7 @@ export async function renderPage(tsxPath, reqUrl, showPanel = true, compiler = '
|
|
|
248
207
|
}
|
|
249
208
|
fs.writeFileSync(serverCjsPath, serverCode, "utf-8");
|
|
250
209
|
writeTempFile(tempPath, JSON.stringify({ clientCode }));
|
|
251
|
-
//console.log(`[authtics] bundle cached for ${path.basename(tsxPath)}`);
|
|
252
210
|
}
|
|
253
|
-
// SSR
|
|
254
211
|
const serverModule = await import(`${serverCjsPath}?t=${Date.now()}`);
|
|
255
212
|
const exports = serverModule.default ?? serverModule;
|
|
256
213
|
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