@bananacool467/authtics-host 0.2.4 → 0.2.6

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 CHANGED
@@ -1,17 +1,11 @@
1
1
  # Authtics Host
2
2
 
3
- Version 0.2.4, License: MIT
3
+ Version 0.2.6, License: MIT
4
4
 
5
- Authtics Host is a web framework inspired by Next.js. Authtics Host supports SSR (Server-Side Rendering) and uses esbuild. This is not to be confused with auth platforms.
5
+ Authtics Host is a web framework inspired by Next.js. Authtics Host supports SSR (Server-Side Rendering), SSG (Static Site Generation), CSR (Client-Side Rendering) and SRTWS (Site Rendering Through WebSockets) and uses **Esbuild**. This is not to be confused with auth platforms.
6
6
 
7
7
  **(NOTE: The short name for Authtics Host is HostJS or Host.js)**
8
8
 
9
- **(From the Developer: I am having 1 small issue:**
10
-
11
- 1. **Web Socket Connections** are **not showing** in the DevPanel's Network tab
12
-
13
- **I need help fixing that)**
14
-
15
9
  ## Setting Up
16
10
 
17
11
  To get **Authtics Host** use:
@@ -51,7 +45,7 @@ import { defineConfig } from "@bananacool467/authtics-host";
51
45
 
52
46
  export default defineConfig({
53
47
  port: 8000,
54
- ssr: true,
48
+ renderType: 'ssr',
55
49
  devMode: true,
56
50
  autoFile: true,
57
51
  autoPage: true,
@@ -94,7 +88,7 @@ import { defineConfig } from "@bananacool467/authtics-host";
94
88
 
95
89
  export default defineConfig({
96
90
  port: 8000,
97
- ssr: true,
91
+ renderType: 'ssr',
98
92
  devMode: true,
99
93
  autoFile: false,
100
94
  autoPage: false
@@ -154,7 +148,7 @@ Then when you run your project, you will get a page just saying **"Hello, World!
154
148
 
155
149
  ## About The Developer Panel
156
150
 
157
- The Developer Panel is what you could use to help you debug your code. The Developer Panel has **7 tabs**:
151
+ The Developer Panel is what you could use to help you debug your code. The Developer Panel has **8 tabs**:
158
152
 
159
153
  * **Console** — Shows current logs.
160
154
  * **Route** — Shows information about the current route.
@@ -163,6 +157,7 @@ The Developer Panel is what you could use to help you debug your code. The Devel
163
157
  * **Client Info** — Shows information about your client's User Agent, OS **(Operating System)** and FPS **(Frames Per Second)**.
164
158
  * **About Host** — Shows the current version of *Authtics Host*.
165
159
  * **Server Info** — Shows how much heap was used and how much CPU was used on the server and how much requests there were and how much users are currently on the site and how long the site has been up.
160
+ * **Panel Customization** — Customize the dev panel.
166
161
 
167
162
  The information the Developer Panel shows outside of the tabs is just your **DAT** (Developer Access Token).
168
163
 
@@ -186,6 +181,17 @@ and then create a file like `paused.tsx` and then inside `App.tsx`, import `Paus
186
181
  ```
187
182
  and if your using `autoPaging` then you just create a file named `_paused.tsx`.
188
183
 
184
+ ## Page Patching
185
+
186
+ Pages may fail to render in SSR if it uses `window` instead of assigning it to a **useState** while in a **useEffect** in *React*
187
+
188
+ You can fix this by adding this to your config file:
189
+ ```json
190
+ "patchForSsr": true
191
+ ```
192
+
193
+ It makes a fake `window`, `document`, etc for SSR when building for Node. Which fixes the issue.
194
+
189
195
  ## App APIs
190
196
 
191
197
  You can add APIs like `get`, `post`, `del` and `put`.
@@ -203,30 +209,10 @@ Hello!
203
209
 
204
210
  ## Notes about Authtics Host
205
211
 
206
- Since Authtics Host is SSR, stuff like NPM packages that uses **Browser APIs** (e.g., `window`, `document`) will cause errors.
207
- The developer (The one that is writing this README) may add a feature where Authtics Host converts:
208
- ```tsx
209
- export function Component() {
210
- return (
211
- <p>{window.__status_code__}</p>
212
- )
213
- }
214
- ```
215
- To:
216
- ```tsx
217
- import { useState, useEffect } from 'react';
218
- export function Component() {
219
- const [__window, __set_window] = useState<any>(null);
220
- useEffect(() => {
221
- if (window) __set_window(window);
222
- }, []);
223
- return (
224
- <p>{__window.__status_code__}</p>
225
- )
226
- }
227
- ```
212
+ The developer might redesign the UI for the DevPanel using [UI Tools](https://npmjs.com/package/@bananakitssu/ui-tools).
228
213
 
229
- The developer might also redesign the UI for the DevPanel using [UI Tools](https://npmjs.com/package/@bananakitssu/ui-tools).
214
+ > **Warning**:
215
+ > SRTWS may not work properly. It may render pages, but not run scripts.
230
216
 
231
217
  > **Warning**:
232
218
  > Asking AI about HostJS is not recommended, it will give an Auth platform and call HostJS/Authtics Host a typo, say it doesn't exist, say it was renamed, it is private, or the account doesn't exist. Or if it knows, it may swap your server script with regular **Node.js**/**ExpressJS**/**Next.js** or use **Next.js-like** code because this framework is **Next.js-inspired**
package/dist/cli.js CHANGED
@@ -4,12 +4,14 @@ import { createJiti } from "jiti";
4
4
  import path from "node:path";
5
5
  import fs from "node:fs";
6
6
  import url from "node:url";
7
- import { renderPage } from "./renderEngine.js";
7
+ import { renderPage, handleSrtwsConnection } from "./renderEngine.js";
8
+ import { tempFileForTsx } from "./rendererTempFiles.js";
8
9
  let config_ = {};
9
10
  let uptime = 0;
10
11
  let totalRequests = 0;
11
12
  let users = 0;
12
13
  let usersPaused = false;
14
+ let srtws;
13
15
  const jiti = createJiti(import.meta.url);
14
16
  const routes = new Map();
15
17
  const __filename = url.fileURLToPath(import.meta.url);
@@ -143,9 +145,9 @@ export default class App {
143
145
  this.startServer();
144
146
  this.post('/__authtics_host_apis__/set-users-paused', async (req, res) => {
145
147
  let datMatched = false;
146
- if (config_.dat && config_.dat == req.cookies.dat)
148
+ if (config_.dat && config_.dat == req.dat)
147
149
  datMatched = true;
148
- if (config_.dat && Array.isArray(config_.dat) && config_.dat.includes(req.cookies.dat))
150
+ if (config_.dat && Array.isArray(config_.dat) && config_.dat.includes(req.dat))
149
151
  datMatched = true;
150
152
  if (datMatched) {
151
153
  usersPaused = req.body.paused;
@@ -157,9 +159,9 @@ export default class App {
157
159
  });
158
160
  this.post('/__authtics_host_apis__/is-users-paused', async (req, res) => {
159
161
  let datMatched = false;
160
- if (config_.dat && config_.dat == req.cookies.dat)
162
+ if (config_.dat && config_.dat == req.dat)
161
163
  datMatched = true;
162
- if (config_.dat && Array.isArray(config_.dat) && config_.dat.includes(req.cookies.dat))
164
+ if (config_.dat && Array.isArray(config_.dat) && config_.dat.includes(req.dat))
163
165
  datMatched = true;
164
166
  res.writeHead(200, { "Content-Type": "application/json" });
165
167
  if (datMatched) {
@@ -172,11 +174,11 @@ export default class App {
172
174
  this.post('/__authtics_host_apis__/get-dat-restriction-missing', async (req, res) => {
173
175
  let datMatched = false;
174
176
  let datRestrictedEnv = false;
175
- if (config_.dat && config_.dat == req.cookies.dat)
177
+ if (config_.dat && config_.dat == req.dat)
176
178
  datMatched = true;
177
179
  if (config_.dat == undefined)
178
180
  datMatched = true;
179
- if (config_.dat && Array.isArray(config_.dat) && config_.dat.includes(req.cookies.dat))
181
+ if (config_.dat && Array.isArray(config_.dat) && config_.dat.includes(req.dat))
180
182
  datMatched = true;
181
183
  if (datMatched) {
182
184
  if (config_.dat !== undefined)
@@ -189,11 +191,11 @@ export default class App {
189
191
  });
190
192
  this.post('/__authtics_host_apis__/get-dat-matching', async (req, res) => {
191
193
  let datMatched = false;
192
- if (config_.dat && config_.dat == req.cookies.dat)
194
+ if (config_.dat && config_.dat == req.dat)
193
195
  datMatched = true;
194
196
  if (config_.dat == undefined)
195
197
  datMatched = true;
196
- if (config_.dat && Array.isArray(config_.dat) && config_.dat.includes(req.cookies.dat))
198
+ if (config_.dat && Array.isArray(config_.dat) && config_.dat.includes(req.dat))
197
199
  datMatched = true;
198
200
  res.writeHead(200, { "Content-Type": "application/json" });
199
201
  res.end(JSON.stringify({
@@ -202,9 +204,9 @@ export default class App {
202
204
  });
203
205
  this.post('/__authtics_host_apis__/get-cpu_memory', async (req, res) => {
204
206
  let datMatched = false;
205
- if (config_.dat && config_.dat == req.cookies.dat)
207
+ if (config_.dat && config_.dat == req.dat)
206
208
  datMatched = true;
207
- if (config_.dat && Array.isArray(config_.dat) && config_.dat.includes(req.cookies.dat))
209
+ if (config_.dat && Array.isArray(config_.dat) && config_.dat.includes(req.dat))
208
210
  datMatched = true;
209
211
  if (datMatched) {
210
212
  const { heapUsed, heapTotal, rss } = process.memoryUsage();
@@ -212,15 +214,16 @@ export default class App {
212
214
  res.writeHead(200, { "Content-Type": "application/json" });
213
215
  res.end(JSON.stringify({
214
216
  cpuUsage,
215
- heapUsed
217
+ heapUsed,
218
+ heapTotal
216
219
  }));
217
220
  }
218
221
  });
219
222
  this.post('/__authtics_host_apis__/get-user-settings', async (req, res) => {
220
223
  let datMatched = false;
221
- if (config_.dat && config_.dat == req.cookies.dat)
224
+ if (config_.dat && config_.dat == req.dat)
222
225
  datMatched = true;
223
- if (config_.dat && Array.isArray(config_.dat) && config_.dat.includes(req.cookies.dat))
226
+ if (config_.dat && Array.isArray(config_.dat) && config_.dat.includes(req.dat))
224
227
  datMatched = true;
225
228
  if (datMatched) {
226
229
  res.writeHead(200, { "Content-Type": "application/json" });
@@ -231,9 +234,9 @@ export default class App {
231
234
  });
232
235
  this.post("/__authtics_host_apis__/get-server-info", async (req, res) => {
233
236
  let datMatched = false;
234
- if (config_.dat && config_.dat == req.cookies.dat)
237
+ if (config_.dat && config_.dat == req.dat)
235
238
  datMatched = true;
236
- if (config_.dat && Array.isArray(config_.dat) && config_.dat.includes(req.cookies.dat))
239
+ if (config_.dat && Array.isArray(config_.dat) && config_.dat.includes(req.dat))
237
240
  datMatched = true;
238
241
  if (datMatched) {
239
242
  res.writeHead(200, { "Content-Type": "application/json" });
@@ -244,9 +247,9 @@ export default class App {
244
247
  });
245
248
  this.post("/__authtics_host_apis__/restart", async (req, res) => {
246
249
  let datMatched = false;
247
- if (config_.dat && config_.dat == req.cookies.dat)
250
+ if (config_.dat && config_.dat == req.dat)
248
251
  datMatched = true;
249
- if (config_.dat && Array.isArray(config_.dat) && config_.dat.includes(req.cookies.dat))
252
+ if (config_.dat && Array.isArray(config_.dat) && config_.dat.includes(req.dat))
250
253
  datMatched = true;
251
254
  if (datMatched) {
252
255
  res.writeHead(200, { "Content-Type": "application/json" });
@@ -257,14 +260,16 @@ export default class App {
257
260
  stdio: 'inherit'
258
261
  });
259
262
  child.unref();
260
- process.kill(process.pid, 'SIGTERM');
263
+ this.server.close(() => {
264
+ process.kill(process.pid, 'SIGTERM');
265
+ });
261
266
  }
262
267
  });
263
268
  this.post("/__authtics_host_apis__/shutdown", async (req, res) => {
264
269
  let datMatched = false;
265
- if (config_.dat && config_.dat == req.cookies.dat)
270
+ if (config_.dat && config_.dat == req.dat)
266
271
  datMatched = true;
267
- if (config_.dat && Array.isArray(config_.dat) && config_.dat.includes(req.cookies.dat))
272
+ if (config_.dat && Array.isArray(config_.dat) && config_.dat.includes(req.dat))
268
273
  datMatched = true;
269
274
  if (datMatched) {
270
275
  res.writeHead(200, { "Content-Type": "application/json" });
@@ -274,24 +279,24 @@ export default class App {
274
279
  });
275
280
  }
276
281
  });
277
- this.post("/__authtics_host_apis__/get-is-ssr", async (req, res) => {
282
+ this.post("/__authtics_host_apis__/get-render-type", async (req, res) => {
278
283
  let datMatched = false;
279
- if (config_.dat && config_.dat == req.cookies.dat)
284
+ if (config_.dat && config_.dat == req.dat)
280
285
  datMatched = true;
281
- if (config_.dat && Array.isArray(config_.dat) && config_.dat.includes(req.cookies.dat))
286
+ if (config_.dat && Array.isArray(config_.dat) && config_.dat.includes(req.dat))
282
287
  datMatched = true;
283
288
  if (datMatched) {
284
289
  res.writeHead(200, { "Content-Type": "application/json" });
285
290
  res.end(JSON.stringify({
286
- isSsr: config_.ssr
291
+ renderType: config_.renderType
287
292
  }));
288
293
  }
289
294
  });
290
295
  this.post("/__authtics_host_apis__/get-compiler", async (req, res) => {
291
296
  let datMatched = false;
292
- if (config_.dat && config_.dat == req.cookies.dat)
297
+ if (config_.dat && config_.dat == req.dat)
293
298
  datMatched = true;
294
- if (config_.dat && Array.isArray(config_.dat) && config_.dat.includes(req.cookies.dat))
299
+ if (config_.dat && Array.isArray(config_.dat) && config_.dat.includes(req.dat))
295
300
  datMatched = true;
296
301
  if (datMatched) {
297
302
  res.writeHead(200, { "Content-Type": "application/json" });
@@ -302,9 +307,9 @@ export default class App {
302
307
  });
303
308
  this.post("/__authtics_host_apis__/get-host-ver", async (req, res) => {
304
309
  let datMatched = false;
305
- if (config_.dat && config_.dat == req.cookies.dat)
310
+ if (config_.dat && config_.dat == req.dat)
306
311
  datMatched = true;
307
- if (config_.dat && Array.isArray(config_.dat) && config_.dat.includes(req.cookies.dat))
312
+ if (config_.dat && Array.isArray(config_.dat) && config_.dat.includes(req.dat))
308
313
  datMatched = true;
309
314
  if (datMatched) {
310
315
  const packageJsonPath = path.join(__dirname, '../package.json');
@@ -320,9 +325,9 @@ export default class App {
320
325
  });
321
326
  this.post("/__authtics_host_apis__/get-file-by-pathname", async (req, res) => {
322
327
  let datMatched = false;
323
- if (config_.dat && config_.dat == req.cookies.dat)
328
+ if (config_.dat && config_.dat == req.dat)
324
329
  datMatched = true;
325
- if (config_.dat && Array.isArray(config_.dat) && config_.dat.includes(req.cookies.dat))
330
+ if (config_.dat && Array.isArray(config_.dat) && config_.dat.includes(req.dat))
326
331
  datMatched = true;
327
332
  if (datMatched) {
328
333
  const pathname = req.body.pathname;
@@ -357,9 +362,9 @@ export default class App {
357
362
  });
358
363
  this.post("/__authtics_host_apis__/ping", async (req, res) => {
359
364
  let datMatched = false;
360
- if (config_.dat && config_.dat == req.cookies.dat)
365
+ if (config_.dat && config_.dat == req.dat)
361
366
  datMatched = true;
362
- if (config_.dat && Array.isArray(config_.dat) && config_.dat.includes(req.cookies.dat))
367
+ if (config_.dat && Array.isArray(config_.dat) && config_.dat.includes(req.dat))
363
368
  datMatched = true;
364
369
  if (datMatched) {
365
370
  res.setHeader("Content-Type", "application/json");
@@ -368,9 +373,9 @@ export default class App {
368
373
  });
369
374
  this.post('/__authtics_host_apis__/get-connection', async (req, res) => {
370
375
  let datMatched = false;
371
- if (config_.dat && config_.dat == req.cookies.dat)
376
+ if (config_.dat && config_.dat == req.dat)
372
377
  datMatched = true;
373
- if (config_.dat && Array.isArray(config_.dat) && config_.dat.includes(req.cookies.dat))
378
+ if (config_.dat && Array.isArray(config_.dat) && config_.dat.includes(req.dat))
374
379
  datMatched = true;
375
380
  if (datMatched) {
376
381
  res.writeHead(200, { "Content-Type": "application/json" });
@@ -418,6 +423,7 @@ async function start() {
418
423
  catch {
419
424
  }
420
425
  const PORT = config.port ?? 3000;
426
+ const renderType = config.renderType ?? 'ssr';
421
427
  const showPanel = config.devMode !== false;
422
428
  const autoPaging = config.autoPage !== false;
423
429
  const compiler = 'esbuild';
@@ -552,7 +558,7 @@ async function start() {
552
558
  }
553
559
  const routeKey = `${method}:${pathname}`;
554
560
  if (routes.has(routeKey)) {
555
- if (Array.isArray(config_.dat) && !config_.dat.includes(req.cookies.dat) && usersPaused && !routeKey.endsWith('.js')) {
561
+ if (Array.isArray(config_.dat) && !config_.dat.includes(req.dat) && usersPaused && !routeKey.endsWith('.js')) {
556
562
  if (autoPaging) {
557
563
  const pausedTsxFile = pagesDir ? path.join(pagesDir, "_paused.tsx") : undefined;
558
564
  if (fs.existsSync(pausedTsxFile || "")) {
@@ -562,7 +568,7 @@ async function start() {
562
568
  res.setHeader("Content-Type", "application/javascript");
563
569
  res.end(f);
564
570
  });
565
- }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat);
571
+ }, (p) => { routes.delete(`GET:${p}`); }, req.dat, config_.dat, config_.renderType, config_.patchForSsr);
566
572
  html = html.replace("<script>", "<script>\nwindow.__paused__ = true;");
567
573
  res.setHeader("Content-Type", "text/html");
568
574
  return res.end(html);
@@ -585,7 +591,7 @@ async function start() {
585
591
  res.setHeader("Content-Type", "application/javascript");
586
592
  res.end(f);
587
593
  });
588
- }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat);
594
+ }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat, config_.renderType);
589
595
  html = html.replace("<script>", "<script>\nwindow.__paused__ = true;");
590
596
  res.setHeader("Content-Type", "text/html");
591
597
  return res.end(html);
@@ -605,7 +611,7 @@ async function start() {
605
611
  message: "unhandled request"
606
612
  }));
607
613
  }
608
- if (config_.dat !== req.cookies.dat && usersPaused && !routeKey.endsWith('.js')) {
614
+ if (config_.dat !== req.dat && usersPaused && !routeKey.endsWith('.js') && !Array.isArray(config_.dat)) {
609
615
  if (autoPaging) {
610
616
  const pausedTsxFile = pagesDir ? path.join(pagesDir, "_paused.tsx") : undefined;
611
617
  if (fs.existsSync(pausedTsxFile || "")) {
@@ -615,7 +621,7 @@ async function start() {
615
621
  res.setHeader("Content-Type", "application/javascript");
616
622
  res.end(f);
617
623
  });
618
- }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat);
624
+ }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat, config_.renderType, config_.patchForSsr);
619
625
  html = html.replace("<script>", "<script>\nwindow.__paused__ = true;");
620
626
  res.setHeader("Content-Type", "text/html");
621
627
  return res.end(html);
@@ -638,7 +644,7 @@ async function start() {
638
644
  res.setHeader("Content-Type", "application/javascript");
639
645
  res.end(f);
640
646
  });
641
- }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat);
647
+ }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat, config_.renderType);
642
648
  html = html.replace("<script>", "<script>\nwindow.__paused__ = true;");
643
649
  res.setHeader("Content-Type", "text/html");
644
650
  return res.end(html);
@@ -679,7 +685,10 @@ async function start() {
679
685
  req.on('close', () => { hmrClients.delete(res); users--; });
680
686
  return;
681
687
  }
682
- if (Array.isArray(config_.dat) && !config_.dat.includes(req.cookies.dat) && usersPaused) {
688
+ if (pathname === '/__authtics_srtws__') {
689
+ return;
690
+ }
691
+ if (Array.isArray(config_.dat) && !config_.dat.includes(req.dat) && usersPaused) {
683
692
  if (autoPaging) {
684
693
  const pausedTsxFile = pagesDir ? path.join(pagesDir, "_paused.tsx") : undefined;
685
694
  if (fs.existsSync(pausedTsxFile || "")) {
@@ -689,7 +698,7 @@ async function start() {
689
698
  res.setHeader("Content-Type", "application/javascript");
690
699
  res.end(f);
691
700
  });
692
- }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat);
701
+ }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat, config_.renderType, config_.patchForSsr);
693
702
  html = html.replace("<script>", "<script>\nwindow.__paused__ = true;");
694
703
  res.setHeader("Content-Type", "text/html");
695
704
  return res.end(html);
@@ -712,7 +721,7 @@ async function start() {
712
721
  res.setHeader("Content-Type", "application/javascript");
713
722
  res.end(f);
714
723
  });
715
- }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat);
724
+ }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat, config_.renderType, config_.patchForSsr);
716
725
  html = html.replace("<script>", "<script>\nwindow.__paused__ = true;");
717
726
  res.setHeader("Content-Type", "text/html");
718
727
  return res.end(html);
@@ -732,7 +741,7 @@ async function start() {
732
741
  message: "unhandled request"
733
742
  }));
734
743
  }
735
- if (config.dat !== req.cookies.dat && usersPaused) {
744
+ if (config.dat !== req.dat && usersPaused && !Array.isArray(config_.dat)) {
736
745
  if (autoPaging) {
737
746
  const pausedTsxFile = pagesDir ? path.join(pagesDir, "_paused.tsx") : undefined;
738
747
  if (fs.existsSync(pausedTsxFile || "")) {
@@ -742,7 +751,7 @@ async function start() {
742
751
  res.setHeader("Content-Type", "application/javascript");
743
752
  res.end(f);
744
753
  });
745
- }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat);
754
+ }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat, config_.renderType, config_.patchForSsr);
746
755
  html = html.replace("<script>", "<script>\nwindow.__paused__ = true;");
747
756
  res.setHeader("Content-Type", "text/html");
748
757
  return res.end(html);
@@ -765,7 +774,7 @@ async function start() {
765
774
  res.setHeader("Content-Type", "application/javascript");
766
775
  res.end(f);
767
776
  });
768
- }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat);
777
+ }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat, config_.renderType, config_.patchForSsr);
769
778
  html = html.replace("<script>", "<script>\nwindow.__paused__ = true;");
770
779
  res.setHeader("Content-Type", "text/html");
771
780
  return res.end(html);
@@ -823,7 +832,7 @@ async function start() {
823
832
  res.setHeader("Content-Type", "application/javascript");
824
833
  res.end(f);
825
834
  });
826
- }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat);
835
+ }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat, config_.renderType, config_.patchForSsr);
827
836
  res.setHeader("Content-Type", "text/html");
828
837
  res.writeHead(404);
829
838
  html = html.replace("<script>", "<script>\nwindow.__status_code__ = 404;");
@@ -842,6 +851,10 @@ async function start() {
842
851
  const urlPath = pathname === "/" ? "/index" : pathname;
843
852
  const tsxPath = pagesDir ? path.join(pagesDir, `${urlPath}.tsx`) : undefined;
844
853
  const htmlPath = pagesDir ? path.join(pagesDir, `${urlPath}.html`) : undefined;
854
+ if (fs.existsSync(tempFileForTsx(tsxPath || ""))) {
855
+ const { default: PageComponent } = await import(tempFileForTsx(tsxPath || "").replace(/\.json$/, ".server.cjs"));
856
+ srtws = handleSrtwsConnection(server, PageComponent);
857
+ }
845
858
  if (tsxPath && fs.existsSync(tsxPath)) {
846
859
  try {
847
860
  const html = await renderPage(tsxPath, url, actuallyShowPanel, compiler, hotReload, autoPaging, config.autoPageDir, (routes.get("APP_TSX") || ""), (f, p) => {
@@ -849,7 +862,7 @@ async function start() {
849
862
  res.setHeader("Content-Type", "application/javascript");
850
863
  res.end(f);
851
864
  });
852
- }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat);
865
+ }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat, config_.renderType, config_.patchForSsr);
853
866
  res.setHeader("Content-Type", "text/html");
854
867
  return res.end(html);
855
868
  }
@@ -873,7 +886,7 @@ async function start() {
873
886
  res.setHeader("Content-Type", "application/javascript");
874
887
  res.end(f);
875
888
  });
876
- }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat);
889
+ }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat, config_.renderType, config_.patchForSsr);
877
890
  res.setHeader("Content-Type", "text/html");
878
891
  return res.end(html);
879
892
  }
@@ -892,7 +905,7 @@ async function start() {
892
905
  res.setHeader("Content-Type", "application/javascript");
893
906
  res.end(f);
894
907
  });
895
- }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat);
908
+ }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat, config_.renderType, config_.patchForSsr);
896
909
  res.setHeader("Content-Type", "text/html");
897
910
  res.writeHead(404);
898
911
  html = html.replace("<script>", "<script>\nwindow.__status_code__ = 404;");
package/dist/config.d.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  export interface AuthticsConfig {
2
2
  port?: number;
3
3
  ssr?: boolean;
4
+ renderType?: 'ssr' | 'csr' | 'ssg' | 'srtws';
4
5
  dir?: string;
5
6
  devMode?: boolean;
6
- compilerType?: 'esbuild' | 'babel';
7
+ compilerType?: 'esbuild';
7
8
  hotReload?: boolean;
8
9
  dat?: string | string[];
9
10
  showError?: boolean;
@@ -12,5 +13,6 @@ export interface AuthticsConfig {
12
13
  autoFile?: boolean;
13
14
  autoFileDir?: string;
14
15
  hasPausedRoute?: boolean;
16
+ patchForSsr?: boolean;
15
17
  }
16
18
  export declare function defineConfig(config: AuthticsConfig): AuthticsConfig;
package/dist/config.js CHANGED
@@ -7,8 +7,14 @@ export function defineConfig(config) {
7
7
  config.showError = true;
8
8
  if (config.compilerType == undefined)
9
9
  config.compilerType = 'esbuild';
10
+ if (config.patchForSsr === undefined)
11
+ config.patchForSsr = false;
10
12
  if (config.ssr == undefined)
11
13
  config.ssr = true;
14
+ else
15
+ console.warn("Using \"ssr\" in Authtics Host Config is deprecated. Use \"renderType\" instead.");
16
+ if (config.renderType == undefined)
17
+ config.renderType = config.ssr ? 'ssr' : 'csr';
12
18
  if (config.port == undefined || config.port == 0)
13
19
  config.port = 3000;
14
20
  if (config.devMode == undefined)
package/dist/devPanel.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useState, useEffect, useRef } from 'react';
3
- import { ShieldCheck, CircleX, ChevronDown, ChevronUp, Bug, AlertTriangle, Info, ArrowLeft, Wifi, Terminal, Route, Globe, ChevronRight, ChevronLeft, Server, TriangleAlert, Eye, Zap, Power } from 'lucide-react';
3
+ import { ShieldCheck, CircleX, ChevronDown, ChevronUp, Bug, AlertTriangle, Info, ArrowLeft, Wifi, Terminal, Route, Globe, ChevronRight, ChevronLeft, Server, Cog, TriangleAlert, Eye, Zap, Power, Check as Tick } from 'lucide-react';
4
4
  function getCallerSource() {
5
5
  try {
6
6
  const stack = new Error().stack ?? '';
@@ -349,8 +349,13 @@ export default function DevPanel() {
349
349
  const [clientInfoOpen, setClientInfoOpen] = useState(false);
350
350
  const [aboutHostOpen, setAboutHostOpen] = useState(false);
351
351
  const [serverInfoOpen, setServerInfoOpen] = useState(false);
352
+ const [panelCustomOpen, setPanelCustomOpen] = useState(false);
352
353
  const [shutdown, setShutdown] = useState(false);
353
354
  const [shuttingDown, setShuttingDown] = useState(false);
355
+ const [panelColor, setPanelColor] = useState({
356
+ background: '#111',
357
+ border: '#333'
358
+ });
354
359
  const [hasIssue, setHasIssue] = useState(false);
355
360
  const [ping__, setPing__] = useState(0);
356
361
  const [networkFetches, setNetworkFetches] = useState([]);
@@ -363,10 +368,11 @@ export default function DevPanel() {
363
368
  const [hostName, setHostName] = useState(undefined);
364
369
  const [protocol, setProtocol] = useState(undefined);
365
370
  const [path, setPath] = useState(undefined);
366
- const [isSsr, setIsSsr] = useState(false);
371
+ const [renderType, setRenderType] = useState("???");
367
372
  const datRestrictionMissingRef = useRef(true);
368
373
  const cpuUsage = useRef(0);
369
374
  const heapUsage = useRef(0);
375
+ const heapTotal = useRef(0);
370
376
  const [datMatch, setDatMatch] = useState(false);
371
377
  const [compilerType, setCompilerType] = useState("?");
372
378
  const [httpVersion, setHttpVersion] = useState("v?.?");
@@ -384,6 +390,7 @@ export default function DevPanel() {
384
390
  const consoleScrollRef = useRef(null);
385
391
  const [isAtBottom, setIsAtBottom] = useState(true);
386
392
  const [datRestrictionMissing, setDatRestrictionMissing] = useState(true);
393
+ const [heapResult, setHeapResult] = useState("0 Bytes / 0 Bytes");
387
394
  const cookie = {
388
395
  set: (k, v) => document.cookie = `${k}=${v}; path=/`,
389
396
  get: (k) => Object.fromEntries(document.cookie.split(";")
@@ -463,15 +470,15 @@ export default function DevPanel() {
463
470
  }
464
471
  return "";
465
472
  };
466
- const isSSR = async () => {
467
- const resp = await fetch('/__authtics_host_apis__/get-is-ssr', {
473
+ const getRenderType = async () => {
474
+ const resp = await fetch('/__authtics_host_apis__/get-render-type', {
468
475
  method: 'POST',
469
476
  headers: { 'Content-Type': 'application/json' },
470
477
  body: JSON.stringify({})
471
478
  });
472
479
  if (resp.status === 200) {
473
480
  const data = await resp.json();
474
- setIsSsr(data.isSsr);
481
+ setRenderType(data.renderType);
475
482
  }
476
483
  };
477
484
  const getUsage = async () => {
@@ -484,6 +491,7 @@ export default function DevPanel() {
484
491
  const data = await resp.json();
485
492
  cpuUsage.current = data.cpuUsage;
486
493
  heapUsage.current = data.heapUsed;
494
+ heapTotal.current = data.heapTotal;
487
495
  }
488
496
  };
489
497
  const getHostVersion = async () => {
@@ -558,7 +566,7 @@ export default function DevPanel() {
558
566
  getPath();
559
567
  getCompilerType();
560
568
  getHttpVersion();
561
- isSSR();
569
+ getRenderType();
562
570
  userSettings();
563
571
  getHostVersion();
564
572
  setInterval(() => {
@@ -630,19 +638,31 @@ export default function DevPanel() {
630
638
  }, 1000);
631
639
  return () => clearInterval(interval);
632
640
  }, []);
641
+ const formatBytes = (b, d = 2, r = false) => {
642
+ if (b === 0)
643
+ return r ? 'Bytes' : '0 Bytes';
644
+ const k = 1024;
645
+ const dm = d < 0 ? 0 : d;
646
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
647
+ const i = Math.floor(Math.log(b) / Math.log(k));
648
+ return r ? sizes[i] : parseFloat((b / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
649
+ };
633
650
  useEffect(() => {
634
651
  const interval = setInterval(() => {
635
652
  const s = pendingSent.current;
636
653
  const r = pendingReceived.current;
637
654
  const c = cpuUsage.current;
638
- const h = heapUsage.current;
655
+ const h = formatBytes(heapUsage.current);
656
+ const t = ` ${formatBytes(heapUsage.current, 0, true)} / ` + formatBytes(heapTotal.current);
657
+ setHeapResult(t);
658
+ const h_num = Number(h.split(" ")[0]);
639
659
  const f = frames.current;
640
660
  pendingSent.current = 0;
641
661
  pendingReceived.current = 0;
642
662
  cpuUsage.current = 0;
643
663
  heapUsage.current = 0;
644
664
  setCpuUsageHistory(prev => [...prev.slice(-(MAX_POINTS - 1)), c]);
645
- setHeapUsageHistory(prev => [...prev.slice(-(MAX_POINTS - 1)), h]);
665
+ setHeapUsageHistory(prev => [...prev.slice(-(MAX_POINTS - 1)), h_num]);
646
666
  setSentHistory(prev => [...prev.slice(-(MAX_POINTS - 1)), s]);
647
667
  setReceivedHistory(prev => [...prev.slice(-(MAX_POINTS - 1)), r]);
648
668
  setFpsHistory(prev => [...prev.slice(-(MAX_POINTS - 1)), f]);
@@ -668,6 +688,16 @@ export default function DevPanel() {
668
688
  };
669
689
  try {
670
690
  const existingToken = cookie.get('dat');
691
+ const panelColor = cookie.get('panelColor');
692
+ if (panelColor) {
693
+ setPanelColor({
694
+ background: panelColor.split('|')[0],
695
+ border: panelColor.split('|')[1]
696
+ });
697
+ }
698
+ else {
699
+ cookie.set('panelColor', '#111|#333');
700
+ }
671
701
  if (existingToken) {
672
702
  setToken(existingToken);
673
703
  let length_ = existingToken?.split('');
@@ -1041,7 +1071,7 @@ export default function DevPanel() {
1041
1071
  border: '1px solid #eee',
1042
1072
  fontSize: '12px',
1043
1073
  color: '#333',
1044
- }, children: [_jsx("span", { style: { fontSize: '12px', color: '#333' }, children: "Renderer Type" }), _jsx("span", { style: { fontSize: '12px', color: 'blue' }, children: isSsr ? 'SSR (Server-Side Rendering)' : 'CSR (Client-Side Rendering)' })] }), _jsxs("div", { style: {
1074
+ }, children: [_jsx("span", { style: { fontSize: '12px', color: '#333' }, children: "Renderer Type" }), _jsx("span", { style: { fontSize: '12px', color: 'blue' }, children: renderType === "ssr" ? 'SSR (Server-Side Rendering)' : renderType === "csr" ? 'CSR (Client-Side Rendering)' : renderType === "ssg" ? 'SSG (Static Site Generation)' : '???' })] }), _jsxs("div", { style: {
1045
1075
  display: 'flex',
1046
1076
  justifyContent: 'space-between',
1047
1077
  padding: '6px 10px',
@@ -1084,7 +1114,46 @@ export default function DevPanel() {
1084
1114
  border: '1px solid #eee',
1085
1115
  fontSize: '12px',
1086
1116
  color: '#333',
1087
- }, children: [_jsx("span", { style: { fontSize: '12px', color: '#333' }, children: "OS" }), _jsx("span", { style: { fontSize: '12px', color: 'blue' }, children: getOS() })] }), _jsx(Sparkline, { data: fpsHistory, color: frames.current < 30 ? '#e53e3e' : frames.current < 60 ? '#d97706' : '#16a34a', label: "FPS", unit: " frames" })] })] })) : aboutHostOpen && !shutdown ? (_jsxs(_Fragment, { children: [_jsxs("div", { style: {
1117
+ }, children: [_jsx("span", { style: { fontSize: '12px', color: '#333' }, children: "OS" }), _jsx("span", { style: { fontSize: '12px', color: 'blue' }, children: getOS() })] }), _jsx(Sparkline, { data: fpsHistory, color: frames.current < 30 ? '#e53e3e' : frames.current < 60 ? '#d97706' : '#16a34a', label: "FPS", unit: " frames" })] })] })) : panelCustomOpen && !shutdown ? (_jsxs(_Fragment, { children: [_jsxs("div", { style: {
1118
+ padding: '6px 8px',
1119
+ borderBottom: '1px solid #ddd',
1120
+ background: '#fff',
1121
+ display: 'flex',
1122
+ alignItems: 'center',
1123
+ gap: '6px',
1124
+ }, children: [_jsxs(PanelButton, { onClick: () => setPanelCustomOpen(false), style: { color: '#555' }, children: [_jsx(ArrowLeft, { size: 12 }), " Back"] }), _jsxs("span", { style: { fontSize: '12px', display: 'flex', alignItems: 'center', gap: '4px', color: '#333' }, children: [_jsx(Cog, { size: 12, color: "#3b82f6" }), " Panel Configuration"] })] }), _jsxs("div", { style: { overflowY: 'auto', maxHeight: '260px', padding: '10px', display: 'flex', flexDirection: 'column', gap: '8px' }, children: [_jsx("span", { style: { fontSize: '12px', color: '#333' }, children: "Colors" }), _jsxs("div", { style: {
1125
+ display: 'flex',
1126
+ justifyContent: 'space-between',
1127
+ padding: '6px 10px',
1128
+ background: '#fff',
1129
+ borderRadius: '10px',
1130
+ border: '1px solid #eee',
1131
+ alignItems: 'center'
1132
+ }, children: [_jsxs("div", { style: { background: '#16a34a', width: '50px', height: '50px', borderRadius: '100px', cursor: 'pointer', alignItems: 'center', textAlign: 'center', justifyContent: 'center', flexDirection: 'column', display: 'flex', border: '1px solid #15803d', gap: '8px' }, onClick: () => {
1133
+ setPanelColor({
1134
+ background: '#16a34a',
1135
+ border: '#15803d'
1136
+ });
1137
+ cookie.set('panelColor', '#16a34a|#15803d');
1138
+ }, children: [panelColor.background === '#16a34a' ? _jsx(Tick, { color: "white", size: 12 }) : _jsx(_Fragment, {}), " ", _jsx("span", { style: { fontSize: '12px', color: '#fff' }, children: "Green" })] }), _jsxs("div", { style: { background: '#111', width: '50px', height: '50px', borderRadius: '100px', cursor: 'pointer', alignItems: 'center', textAlign: 'center', justifyContent: 'center', flexDirection: 'column', display: 'flex', border: '1px solid #333', gap: '8px' }, onClick: () => {
1139
+ setPanelColor({
1140
+ background: '#111',
1141
+ border: '#333'
1142
+ });
1143
+ cookie.set('panelColor', '#111|#333');
1144
+ }, children: [panelColor.background === '#111' ? _jsx(Tick, { color: "white", size: 12 }) : _jsx(_Fragment, {}), " ", _jsx("span", { style: { fontSize: '12px', color: '#fff' }, children: "Black" })] }), _jsxs("div", { style: { background: '#f7c623', width: '50px', height: '50px', borderRadius: '100px', cursor: 'pointer', alignItems: 'center', textAlign: 'center', justifyContent: 'center', flexDirection: 'column', display: 'flex', border: '1px solid #d9ad1e', gap: '8px' }, onClick: () => {
1145
+ setPanelColor({
1146
+ background: '#f7c623',
1147
+ border: '#d9ad1e'
1148
+ });
1149
+ cookie.set('panelColor', '#f7c623|#d9ad1e');
1150
+ }, children: [panelColor.background === '#f7c623' ? _jsx(Tick, { color: "white", size: 12 }) : _jsx(_Fragment, {}), " ", _jsx("span", { style: { fontSize: '12px', color: '#fff' }, children: "Yellow" })] }), _jsxs("div", { style: { background: '#3bb0ff', width: '50px', height: '50px', borderRadius: '100px', cursor: 'pointer', alignItems: 'center', textAlign: 'center', justifyContent: 'center', flexDirection: 'column', display: 'flex', border: '1px solid #3290cf', gap: '8px' }, onClick: () => {
1151
+ setPanelColor({
1152
+ background: '#3bb0ff',
1153
+ border: '#3290cf'
1154
+ });
1155
+ cookie.set('panelColor', '#3bb0ff|#3290cf');
1156
+ }, children: [panelColor.background === '#3bb0ff' ? _jsx(Tick, { color: "white", size: 12 }) : _jsx(_Fragment, {}), " ", _jsx("span", { style: { fontSize: '12px', color: '#fff' }, children: "Blue" })] })] })] })] })) : aboutHostOpen && !shutdown ? (_jsxs(_Fragment, { children: [_jsxs("div", { style: {
1088
1157
  padding: '6px 8px',
1089
1158
  borderBottom: '1px solid #ddd',
1090
1159
  background: '#fff',
@@ -1154,7 +1223,7 @@ export default function DevPanel() {
1154
1223
  border: '1px solid #eee',
1155
1224
  fontSize: '12px',
1156
1225
  color: '#333',
1157
- }, children: [_jsx("span", { style: { fontSize: '12px', color: '#333' }, children: "Total current users" }), _jsx("span", { style: { fontSize: '12px', color: 'blue' }, children: users })] }), _jsx(Sparkline, { data: cpuUsageHistory, color: "#3b82f6", label: "CPU Usage", unit: "" }), _jsx(Sparkline, { data: heapUsageHistory, color: "#3b82f6", label: "Heap Used", unit: "" })] })] })) : !shutdown ? (_jsxs(_Fragment, { children: [_jsxs("div", { style: { padding: '8px', gap: '4px', display: 'flex', flexDirection: 'column' }, children: [_jsxs(PanelButton, { onClick: () => setConsoleOpen(true), fullWidth: true, style: { color: '#333', justifyContent: 'space-between', height: '32px' }, children: [_jsxs("span", { style: { display: 'flex', alignItems: 'center', gap: '6px' }, children: [_jsx(Terminal, { size: 12, color: "#16a34a" }), " Console"] }), logs.length > 0 && (_jsx("span", { style: { color: '#111', borderRadius: '100px', padding: '1px 6px', fontSize: '10px' }, children: logs.length }))] }), _jsxs(PanelButton, { onClick: () => setRouteOpen(true), fullWidth: true, style: { color: '#333', justifyContent: 'space-between', height: '32px' }, children: [_jsxs("span", { style: { display: 'flex', alignItems: 'center', gap: '6px' }, children: [_jsx(Route, { size: 12, color: "#3b82f6" }), " Route"] }), _jsxs("span", { style: { color: '#111', borderRadius: '100px', padding: '1px 6px', fontSize: '10px', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '4px' }, children: [datRestrictionMissing && _jsx(TriangleAlert, { size: 12, color: "orange" }), pathName] })] }), _jsxs(PanelButton, { onClick: () => setWebpageInfoOpen(true), fullWidth: true, style: { color: '#333', justifyContent: 'space-between', height: '32px' }, children: [_jsxs("span", { style: { display: 'flex', alignItems: 'center', gap: '6px' }, children: [_jsx(Globe, { size: 12, color: "#3b82f6" }), " Webpage Info"] }), datRestrictionMissing && _jsx(TriangleAlert, { size: 12, color: "orange" })] }), _jsx(PanelButton, { onClick: () => setNetworkOpen(true), fullWidth: true, style: { color: '#333', justifyContent: 'space-between', height: '32px' }, children: _jsxs("span", { style: { display: 'flex', alignItems: 'center', gap: '6px' }, children: [_jsx(Wifi, { size: 12, color: "#3b82f6" }), " Network"] }) }), _jsxs(PanelButton, { onClick: () => setClientInfoOpen(true), fullWidth: true, style: { color: '#333', justifyContent: 'space-between', height: '32px' }, children: [_jsxs("span", { style: { display: 'flex', alignItems: 'center', gap: '6px' }, children: [_jsx(Info, { size: 12, color: "#3b82f6" }), " Client Info"] }), datRestrictionMissing && _jsx(TriangleAlert, { size: 12, color: "orange" })] }), _jsxs(PanelButton, { onClick: () => setAboutHostOpen(true), fullWidth: true, style: { color: '#333', justifyContent: 'space-between', height: '32px' }, children: [_jsxs("span", { style: { display: 'flex', alignItems: 'center', gap: '6px' }, children: [_jsx(ShieldCheck, { size: 12, color: "#f472b6" }), " About HostJS"] }), datRestrictionMissing && _jsx(TriangleAlert, { size: 12, color: "orange" })] }), _jsxs(PanelButton, { onClick: () => setServerInfoOpen(true), fullWidth: true, style: { color: '#333', justifyContent: 'space-between', height: '32px' }, children: [_jsxs("span", { style: { display: 'flex', alignItems: 'center', gap: '6px' }, children: [_jsx(Server, { size: 12, color: "#f472b6" }), " Server Info"] }), datRestrictionMissing && _jsx(TriangleAlert, { size: 12, color: "orange" })] })] }), _jsxs("div", { style: {
1226
+ }, children: [_jsx("span", { style: { fontSize: '12px', color: '#333' }, children: "Total current users" }), _jsx("span", { style: { fontSize: '12px', color: 'blue' }, children: users })] }), _jsx(Sparkline, { data: cpuUsageHistory, color: "#3b82f6", label: "CPU Usage", unit: "" }), _jsx(Sparkline, { data: heapUsageHistory, color: "#3b82f6", label: "Heap Used", unit: heapResult })] })] })) : !shutdown ? (_jsxs(_Fragment, { children: [_jsxs("div", { style: { padding: '8px', gap: '4px', display: 'flex', flexDirection: 'column' }, children: [_jsxs(PanelButton, { onClick: () => setConsoleOpen(true), fullWidth: true, style: { color: '#333', justifyContent: 'space-between', height: '32px' }, children: [_jsxs("span", { style: { display: 'flex', alignItems: 'center', gap: '6px' }, children: [_jsx(Terminal, { size: 12, color: "#16a34a" }), " Console"] }), logs.length > 0 && (_jsx("span", { style: { color: '#111', borderRadius: '100px', padding: '1px 6px', fontSize: '10px' }, children: logs.length }))] }), _jsxs(PanelButton, { onClick: () => setRouteOpen(true), fullWidth: true, style: { color: '#333', justifyContent: 'space-between', height: '32px' }, children: [_jsxs("span", { style: { display: 'flex', alignItems: 'center', gap: '6px' }, children: [_jsx(Route, { size: 12, color: "#3b82f6" }), " Route"] }), _jsxs("span", { style: { color: '#111', borderRadius: '100px', padding: '1px 6px', fontSize: '10px', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '4px' }, children: [datRestrictionMissing && _jsx(TriangleAlert, { size: 12, color: "orange" }), pathName] })] }), _jsxs(PanelButton, { onClick: () => setWebpageInfoOpen(true), fullWidth: true, style: { color: '#333', justifyContent: 'space-between', height: '32px' }, children: [_jsxs("span", { style: { display: 'flex', alignItems: 'center', gap: '6px' }, children: [_jsx(Globe, { size: 12, color: "#3b82f6" }), " Webpage Info"] }), datRestrictionMissing && _jsx(TriangleAlert, { size: 12, color: "orange" })] }), _jsx(PanelButton, { onClick: () => setPanelCustomOpen(true), fullWidth: true, style: { color: '#333', justifyContent: 'space-between', height: '32px' }, children: _jsxs("span", { style: { display: 'flex', alignItems: 'center', gap: '6px' }, children: [_jsx(Cog, { size: 12, color: "#3b82f6" }), " Panel Customization"] }) }), _jsx(PanelButton, { onClick: () => setNetworkOpen(true), fullWidth: true, style: { color: '#333', justifyContent: 'space-between', height: '32px' }, children: _jsxs("span", { style: { display: 'flex', alignItems: 'center', gap: '6px' }, children: [_jsx(Wifi, { size: 12, color: "#3b82f6" }), " Network"] }) }), _jsxs(PanelButton, { onClick: () => setClientInfoOpen(true), fullWidth: true, style: { color: '#333', justifyContent: 'space-between', height: '32px' }, children: [_jsxs("span", { style: { display: 'flex', alignItems: 'center', gap: '6px' }, children: [_jsx(Info, { size: 12, color: "#3b82f6" }), " Client Info"] }), datRestrictionMissing && _jsx(TriangleAlert, { size: 12, color: "orange" })] }), _jsxs(PanelButton, { onClick: () => setAboutHostOpen(true), fullWidth: true, style: { color: '#333', justifyContent: 'space-between', height: '32px' }, children: [_jsxs("span", { style: { display: 'flex', alignItems: 'center', gap: '6px' }, children: [_jsx(ShieldCheck, { size: 12, color: "#f472b6" }), " About HostJS"] }), datRestrictionMissing && _jsx(TriangleAlert, { size: 12, color: "orange" })] }), _jsxs(PanelButton, { onClick: () => setServerInfoOpen(true), fullWidth: true, style: { color: '#333', justifyContent: 'space-between', height: '32px' }, children: [_jsxs("span", { style: { display: 'flex', alignItems: 'center', gap: '6px' }, children: [_jsx(Server, { size: 12, color: "#f472b6" }), " Server Info"] }), datRestrictionMissing && _jsx(TriangleAlert, { size: 12, color: "orange" })] })] }), _jsxs("div", { style: {
1158
1227
  padding: '6px 8px',
1159
1228
  borderTop: '1px solid #ddd',
1160
1229
  background: '#fff',
@@ -1214,8 +1283,8 @@ export default function DevPanel() {
1214
1283
  alignItems: 'center',
1215
1284
  padding: '8px 12px',
1216
1285
  borderRadius: hasIssue ? '100px' : '10px',
1217
- background: hasIssue ? '#ba4e4e' : '#111',
1218
- border: hasIssue ? '1px solid #991b1b' : '1px solid #333',
1286
+ background: hasIssue ? '#ba4e4e' : panelColor.background,
1287
+ border: hasIssue ? '1px solid #991b1b' : `1px solid ${panelColor.border ?? '#333'}`,
1219
1288
  boxShadow: hasIssue
1220
1289
  ? '0 4px 20px rgba(220,38,38,0.2)'
1221
1290
  : '0 4px 20px rgba(0,0,0,0.4)',
@@ -1,3 +1,6 @@
1
+ import React from "react";
2
+ export declare function handleSrtwsConnection(server: any, PageComponent: React.ComponentType<any>): boolean;
1
3
  export type CompilerType = 'esbuild';
4
+ export type RenderMode = 'ssr' | 'csr' | 'ssg' | 'srtws';
2
5
  export declare function findFileUpwards(startDir: string, relativePath: string): string | null;
3
- export declare function renderPage(tsxPath: string, reqUrl?: string, showPanel?: boolean, compiler?: CompilerType, hotReload?: boolean, autoPaging?: boolean, autoPageDir?: string, appDir?: string, fileHandler?: (f: string, p: string) => void, rmvFileHandler?: (p: string) => void, reqDAT?: string | undefined, configDAT?: string | string[] | undefined): Promise<string>;
6
+ export declare function renderPage(tsxPath: string, reqUrl?: string, showPanel?: boolean, compiler?: CompilerType, hotReload?: boolean, autoPaging?: boolean, autoPageDir?: string, appDir?: string, fileHandler?: (f: string, p: string) => void, rmvFileHandler?: (p: string) => void, reqDAT?: string | undefined, configDAT?: string | string[] | undefined, renderMode?: RenderMode, patchForSsr?: boolean): Promise<string>;
@@ -7,6 +7,32 @@ import { renderToString } from "react-dom/server";
7
7
  import { tempFileForTsx, writeTempFile, readTempFile, ensureTempDirs, } from "./rendererTempFiles.js";
8
8
  import { createRequire } from "node:module";
9
9
  const customRequire = createRequire(import.meta.url);
10
+ import { WebSocketServer } from "ws";
11
+ export function handleSrtwsConnection(server, PageComponent) {
12
+ const wss = new WebSocketServer({ noServer: true });
13
+ server.on("upgrade", (req, socket, head) => {
14
+ if (req.url === "/__authtics_srtws__") {
15
+ wss.handleUpgrade(req, socket, head, (ws) => {
16
+ wss.emit("connection", ws, req);
17
+ });
18
+ }
19
+ });
20
+ wss.on("connection", (ws) => {
21
+ const renderAndSend = () => {
22
+ const pageHtml = renderToString(React.createElement(PageComponent));
23
+ ws.send(JSON.stringify({ type: "DOM", html: pageHtml }));
24
+ };
25
+ ws.on("message", () => {
26
+ try {
27
+ renderAndSend();
28
+ }
29
+ catch (err) {
30
+ console.error("[authtics] SRTWS error:", err);
31
+ }
32
+ });
33
+ });
34
+ return true;
35
+ }
10
36
  const __filename = fileURLToPath(import.meta.url);
11
37
  const __dirname = path.dirname(__filename);
12
38
  const NODE_BUILTINS = [
@@ -23,6 +49,24 @@ const NODE_BUILTINS = [
23
49
  'node:module', 'node:process', 'node:string_decoder', 'node:querystring',
24
50
  'express', 'jiti',
25
51
  ];
52
+ const SRTWS_CLIENT_SCRIPT = `<script>
53
+ (function() {
54
+ const protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
55
+ const ws = new WebSocket(protocol + window.location.host + '/__authtics_srtws__');
56
+
57
+ ws.onmessage = function(event) {
58
+ const msg = JSON.parse(event.data);
59
+ if (msg.type === 'DOM') {
60
+ const root = document.getElementById('root');
61
+ if (root) root.innerHTML = msg.html;
62
+ }
63
+ };
64
+
65
+ ws.onopen = function() {
66
+ ws.send(JSON.stringify({}));
67
+ };
68
+ })();
69
+ </script>`;
26
70
  const nodeStubPlugin = {
27
71
  name: 'browser-node-stub',
28
72
  setup(build) {
@@ -42,7 +86,7 @@ const nodeStubPlugin = {
42
86
  }));
43
87
  },
44
88
  };
45
- async function buildWithEsbuild(virtualContent, resolveDir, platform) {
89
+ async function buildWithEsbuild(virtualContent, resolveDir, platform, patchForSsr = false) {
46
90
  const toFwdSlash = (p) => p.split(path.sep).join('/');
47
91
  const isNode = platform === 'node';
48
92
  const result = await esbuild.build({
@@ -56,10 +100,86 @@ async function buildWithEsbuild(virtualContent, resolveDir, platform) {
56
100
  format: isNode ? 'cjs' : 'iife',
57
101
  platform,
58
102
  jsx: 'automatic',
59
- sourcemap: isNode ? false : false,
103
+ sourcemap: false,
60
104
  target: isNode ? ['node18'] : ['es2020'],
61
105
  minify: true,
62
106
  treeShaking: true,
107
+ banner: (isNode && patchForSsr) ? {
108
+ js: `if (typeof globalThis.window === "undefined") {
109
+ const noop = () => {};
110
+
111
+ const createStorageMock = () => {
112
+ let store = {};
113
+ return {
114
+ getItem: (key) => store[key] ?? null,
115
+ setItem: (key, val) => { store[key] = String(val); },
116
+ removeItem: (key) => { delete store[key]; },
117
+ clear: () => { store = {}; },
118
+ key: (i) => Object.keys(store)[i] ?? null,
119
+ get length() { return Object.keys(store).length; },
120
+ };
121
+ };
122
+
123
+ const mockLocalStorage = createStorageMock();
124
+ const mockSessionStorage = createStorageMock();
125
+
126
+ const mockElement = {
127
+ setAttribute: noop,
128
+ getAttribute: () => null,
129
+ removeAttribute: noop,
130
+ style: {},
131
+ appendChild: (node) => node,
132
+ removeChild: (node) => node,
133
+ addEventListener: noop,
134
+ removeEventListener: noop,
135
+ classList: { add: noop, remove: noop, toggle: noop, contains: () => false },
136
+ };
137
+
138
+ const mockDocument = {
139
+ createElement: () => mockElement,
140
+ querySelector: () => null,
141
+ querySelectorAll: () => [],
142
+ getElementById: () => null,
143
+ getElementsByTagName: () => [],
144
+ getElementsByClassName: () => [],
145
+ cookie: "",
146
+ body: mockElement,
147
+ documentElement: mockElement,
148
+ addEventListener: noop,
149
+ removeEventListener: noop,
150
+ };
151
+
152
+ const mockNavigator = {
153
+ userAgent: "Mozilla/5.0 (Server-Side)",
154
+ language: "en-US",
155
+ languages: ["en-US"],
156
+ onLine: true,
157
+ clipboard: { writeText: async () => {}, readText: async () => "" },
158
+ };
159
+
160
+
161
+ globalThis.document = globalThis.document || mockDocument;
162
+ globalThis.navigator = globalThis.navigator || mockNavigator;
163
+ globalThis.localStorage = globalThis.localStorage || mockLocalStorage;
164
+ globalThis.sessionStorage = globalThis.sessionStorage || mockSessionStorage;
165
+
166
+ globalThis.window = new Proxy(globalThis, {
167
+ get: (target, prop) => {
168
+ if (prop in target) return target[prop];
169
+ if (prop === 'document') return globalThis.document;
170
+ if (prop === 'navigator') return globalThis.navigator;
171
+ if (prop === 'localStorage') return globalThis.localStorage;
172
+ if (prop === 'sessionStorage') return globalThis.sessionStorage;
173
+ if (prop === 'addEventListener' || prop === 'removeEventListener') return noop;
174
+ if (prop === 'location') return { href: '', search: '', pathname: '/', origin: '', assign: noop, replace: noop };
175
+ if (prop === 'matchMedia') return () => ({ matches: false, addEventListener: noop, removeEventListener: noop, dispatchEvent: noop });
176
+ if (prop === 'getComputedStyle') return () => ({ getPropertyValue: () => '' });
177
+ if (prop === 'requestAnimationFrame' || prop === 'cancelAnimationFrame') return (cb) => setTimeout(cb, 0);
178
+ return undefined;
179
+ }
180
+ });
181
+ }`
182
+ } : undefined,
63
183
  plugins: isNode ? [] : [nodeStubPlugin],
64
184
  external: isNode
65
185
  ? ['react', 'react-dom', 'react-dom/server', 'react/jsx-runtime', 'react/jsx-dev-runtime',
@@ -108,17 +228,18 @@ function resolveTemplate(appDir, autoPageDir, autoPaging) {
108
228
  return nextToCli;
109
229
  }
110
230
  function buildHtml(opts) {
111
- const { pageHtml, clientCode, devPanelContainer, templatePath, hotReload, fileName, datRestricted, devMode } = opts;
231
+ const { pageHtml, clientCode, devPanelContainer, templatePath, hotReload, fileName, datRestricted, devMode, renderMode } = opts;
112
232
  const configScript = `<script>window.__authtics_config__={datRestricted:${datRestricted ? 'true' : 'false'},devMode:${devMode !== false ? 'true' : 'false'}};</script>`;
113
233
  const hmrScript = hotReload ? HMR_SCRIPT : '';
234
+ const srtwsScript = renderMode === 'srtws' ? SRTWS_CLIENT_SCRIPT : '';
114
235
  if (templatePath && fs.existsSync(templatePath)) {
115
236
  let template = fs.readFileSync(templatePath, "utf-8");
116
237
  const contentTag = /<authtics_host_content\s*\/>/i;
117
238
  if (contentTag.test(template)) {
118
239
  template = template.replace(contentTag, `<div id="root">${pageHtml}</div>\n ${devPanelContainer}`);
119
240
  template = template.includes("</body>")
120
- ? template.replace("</body>", ` ${configScript}${fileName ? `<script src="${fileName}"></script>` : `<script>${clientCode}</script>`}\n${hmrScript}</body>`)
121
- : template + `\n${configScript}${fileName ? `<script src="${fileName}"></script>` : `<script>${clientCode}</script>`}\n${hmrScript}`;
241
+ ? template.replace("</body>", ` ${configScript}${srtwsScript}${fileName ? `<script src="${fileName}"></script>` : `<script>${clientCode}</script>`}\n${hmrScript}</body>`)
242
+ : template + `\n${configScript}${srtwsScript}${fileName ? `<script src="${fileName}"></script>` : `<script>${clientCode}</script>`}\n${hmrScript}`;
122
243
  return template;
123
244
  }
124
245
  console.warn('[authtics] _template.html has no <authtics_host_content /> — using default layout.');
@@ -133,7 +254,8 @@ function buildHtml(opts) {
133
254
  <div id="root">${pageHtml}</div>
134
255
  ${devPanelContainer}
135
256
  ${configScript}
136
- ${fileName ? `<script src="${fileName}"></script>` : `<script>${clientCode}</script>`}
257
+ ${renderMode === 'srtws' ? '' : (fileName ? `<script src="${fileName}"></script>` : `<script>${clientCode}</script>`)}
258
+ ${srtwsScript}
137
259
  ${hmrScript}
138
260
  </body>
139
261
  </html>`;
@@ -180,7 +302,7 @@ const HMR_SCRIPT = `<script>(function(){
180
302
  }
181
303
  };
182
304
  })();</script>`;
183
- export async function renderPage(tsxPath, reqUrl, showPanel = true, compiler = 'esbuild', hotReload = false, autoPaging = true, autoPageDir, appDir, fileHandler, rmvFileHandler, reqDAT, configDAT) {
305
+ export async function renderPage(tsxPath, reqUrl, showPanel = true, compiler = 'esbuild', hotReload = false, autoPaging = true, autoPageDir, appDir, fileHandler, rmvFileHandler, reqDAT, configDAT, renderMode = 'ssr', patchForSsr = false) {
184
306
  ensureTempDirs();
185
307
  if (!fs.existsSync(tsxPath)) {
186
308
  throw new Error(`renderPage: source file not found: ${tsxPath}`);
@@ -198,6 +320,14 @@ export async function renderPage(tsxPath, reqUrl, showPanel = true, compiler = '
198
320
  const templatePath = resolveTemplate(appDir, autoPageDir, autoPaging);
199
321
  const tempPath = tempFileForTsx(tsxPath);
200
322
  const serverCjsPath = tempPath.replace(/\.json$/, ".server.cjs");
323
+ const ssgHtmlPath = tempPath.replace(/\.json$/, ".ssg.html");
324
+ if (renderMode === 'ssg' && isCacheValid(tsxPath, ssgHtmlPath, serverCjsPath, templatePath, devPanelPath)) {
325
+ try {
326
+ return fs.readFileSync(ssgHtmlPath, "utf-8");
327
+ }
328
+ catch {
329
+ }
330
+ }
201
331
  let clientCode;
202
332
  let serverCode;
203
333
  let freshWrite = false;
@@ -210,7 +340,8 @@ export async function renderPage(tsxPath, reqUrl, showPanel = true, compiler = '
210
340
  catch {
211
341
  }
212
342
  }
213
- if (!clientCode || !serverCode) {
343
+ const isCsr = renderMode === 'csr';
344
+ if (!clientCode || (!serverCode && !isCsr)) {
214
345
  const virtualServer = `
215
346
  ${usesDevPanel ? `import DevPanel from ${JSON.stringify(devPanelPath)};` : ""}
216
347
  import Page from ${JSON.stringify(tsxPath)};
@@ -219,49 +350,71 @@ export async function renderPage(tsxPath, reqUrl, showPanel = true, compiler = '
219
350
  `;
220
351
  const virtualClient = `
221
352
  import React from "react";
222
- import { hydrateRoot } from "react-dom/client";
353
+ import { ${renderMode === 'csr' ? 'createRoot' : 'hydrateRoot'} } from "react-dom/client";
223
354
  ${usesDevPanel ? `import DevPanel from ${JSON.stringify(devPanelPath)};` : ""}
224
355
  import Page from ${JSON.stringify(tsxPath)};
225
356
 
226
357
  ${usesDevPanel ? `
227
358
  const panelEl = document.getElementById("authtics-dev-panel-root");
228
- if (panelEl) hydrateRoot(panelEl, React.createElement(DevPanel));
359
+ if (panelEl) ${renderMode === 'csr' ? `createRoot(panelEl).render(React.createElement(DevPanel));` : `hydrateRoot(panelEl, React.createElement(DevPanel));`}
229
360
  ` : ""}
230
361
  const rootEl = document.getElementById("root");
231
- if (rootEl) hydrateRoot(rootEl, React.createElement(Page));
362
+ if (rootEl) {
363
+ ${renderMode === 'csr'
364
+ ? `createRoot(rootEl).render(React.createElement(Page));`
365
+ : `hydrateRoot(rootEl, React.createElement(Page));`}
366
+ }
232
367
  `;
233
368
  if (compiler === 'esbuild') {
234
- serverCode = await buildWithEsbuild(virtualServer, process.cwd(), 'node');
235
- clientCode = await buildWithEsbuild(virtualClient, process.cwd(), 'browser');
369
+ if (!isCsr)
370
+ serverCode = await buildWithEsbuild(virtualServer, process.cwd(), 'node', patchForSsr);
371
+ if (renderMode !== 'srtws') {
372
+ clientCode = await buildWithEsbuild(virtualClient, process.cwd(), 'browser');
373
+ }
374
+ else {
375
+ clientCode = "";
376
+ }
236
377
  }
237
- await fs.promises.writeFile(serverCjsPath, serverCode || "", "utf-8");
378
+ if (serverCode)
379
+ await fs.promises.writeFile(serverCjsPath, serverCode || "", "utf-8");
238
380
  writeTempFile(tempPath, JSON.stringify({ clientCode }));
239
381
  freshWrite = true;
240
382
  }
241
383
  fileHandler ? fileHandler(clientCode || "", `/${path.basename(tempPath).replace(".json", ".js").replace(".tsx_", ".").toLowerCase()}`) : null;
242
- const absoluteServerPath = path.resolve(serverCjsPath);
243
- delete customRequire.cache[absoluteServerPath];
244
- const serverModule = customRequire(absoluteServerPath);
245
- const exports = serverModule.__esModule ? serverModule.default : serverModule;
246
- const PageComponent = exports?.default?.default ??
247
- exports?.default ??
248
- exports?.Page ??
249
- exports?.page ??
250
- (typeof exports === 'function' ? exports : null);
251
- const DevPanelComponent = usesDevPanel ? (exports.DevPanel ?? null) : null;
252
- if (!PageComponent) {
253
- throw new Error(`renderPage: no default export found in ${tsxPath}`);
254
- }
255
- const pageHtml = renderToString(React.createElement(PageComponent));
256
- const devPanelHtml = DevPanelComponent
257
- ? renderToString(React.createElement(DevPanelComponent))
258
- : "";
259
- const devPanelContainer = usesDevPanel
260
- ? `<div id="authtics-dev-panel-root">${devPanelHtml}</div>`
384
+ let pageHtml = "";
385
+ let devPanelContainer = usesDevPanel
386
+ ? `<div id="authtics-dev-panel-root"></div>`
261
387
  : "";
262
- return buildHtml({ pageHtml, clientCode: clientCode || "", devPanelContainer, templatePath, hotReload,
388
+ if (!isCsr) {
389
+ const absoluteServerPath = path.resolve(serverCjsPath);
390
+ delete customRequire.cache[absoluteServerPath];
391
+ const serverModule = customRequire(absoluteServerPath);
392
+ const exports = serverModule.__esModule ? serverModule.default : serverModule;
393
+ const PageComponent = exports?.default?.default ??
394
+ exports?.default ??
395
+ exports?.Page ??
396
+ exports?.page ??
397
+ (typeof exports === 'function' ? exports : null);
398
+ const DevPanelComponent = usesDevPanel ? (exports.DevPanel ?? null) : null;
399
+ if (!PageComponent) {
400
+ throw new Error(`renderPage: no default export found in ${tsxPath}`);
401
+ }
402
+ pageHtml = renderToString(React.createElement(PageComponent));
403
+ const devPanelHtml = DevPanelComponent
404
+ ? renderToString(React.createElement(DevPanelComponent))
405
+ : "";
406
+ devPanelContainer = usesDevPanel
407
+ ? `<div id="authtics-dev-panel-root">${devPanelHtml}</div>`
408
+ : "";
409
+ }
410
+ const finalHtml = buildHtml({ pageHtml, clientCode: clientCode || "", devPanelContainer, templatePath, hotReload,
263
411
  fileName: `/${path.basename(tempPath).replace(".json", ".js").replace(".tsx_", ".").toLowerCase()}`,
264
412
  datRestricted: configDAT !== undefined,
265
413
  devMode: showPanel,
414
+ renderMode
266
415
  });
416
+ if (renderMode === 'ssg') {
417
+ await fs.promises.writeFile(ssgHtmlPath, finalHtml, "utf-8");
418
+ }
419
+ return finalHtml;
267
420
  }
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@bananacool467/authtics-host",
3
- "version": "0.2.4",
4
- "description": "Authtics Host, A Next.js-inspired SSR framework for Node.js with TypeScript and JSX support.",
3
+ "version": "0.2.6",
4
+ "description": "Authtics Host, A Next.js-inspired web framework for Node.js and React with TypeScript and JSX support.",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
7
7
  "type": "module",
8
8
  "sideEffects": false,
9
9
  "bin": {
10
- "hostjs": "dist/cli.js"
10
+ "host": "dist/cli.js"
11
11
  },
12
12
  "exports": {
13
13
  ".": "./dist/index.js"
@@ -25,6 +25,9 @@
25
25
  "host",
26
26
  "server-side-rendering",
27
27
  "ssr",
28
+ "ssg",
29
+ "csr",
30
+ "srtws",
28
31
  "framework",
29
32
  "nodejs",
30
33
  "typescript",
@@ -44,26 +47,28 @@
44
47
  "author": "bananacool467",
45
48
  "license": "MIT",
46
49
  "dependencies": {
47
- "esbuild": "^0.27.3",
50
+ "esbuild": "^0.28.2",
48
51
  "jiti": "^2.6.1",
49
52
  "lucide-react": "^0.577.0",
50
- "prismjs": "^1.30.0"
53
+ "prismjs": "^1.30.0",
54
+ "ws": "^8.21.3"
51
55
  },
52
56
  "peerDependencies": {
53
57
  "react": "^19.2.4",
54
58
  "react-dom": "^19.2.4"
55
59
  },
56
60
  "devDependencies": {
61
+ "@babel/core": "^7.29.0",
62
+ "@babel/preset-env": "^7.29.0",
63
+ "@babel/preset-react": "^7.28.5",
64
+ "@babel/preset-typescript": "^7.28.5",
65
+ "@types/babel__core": "^7.20.5",
66
+ "@types/babel__preset-env": "^7.10.0",
57
67
  "@types/node": "^22.13.11",
58
68
  "@types/prismjs": "^1.26.6",
59
69
  "@types/react": "^19.2.14",
60
70
  "@types/react-dom": "^19.2.3",
61
71
  "@types/react-syntax-highlighter": "^15.5.13",
62
- "@types/babel__core": "^7.20.5",
63
- "@types/babel__preset-env": "^7.10.0",
64
- "@babel/core": "^7.29.0",
65
- "@babel/preset-env": "^7.29.0",
66
- "@babel/preset-react": "^7.28.5",
67
- "@babel/preset-typescript": "^7.28.5"
72
+ "@types/ws": "^8.18.1"
68
73
  }
69
74
  }