@bananacool467/authtics-host 0.2.5 → 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.5, 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,32 +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).
230
-
231
- And also maybe, add Developer Panel customization.
214
+ > **Warning**:
215
+ > SRTWS may not work properly. It may render pages, but not run scripts.
232
216
 
233
217
  > **Warning**:
234
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);
@@ -277,7 +279,7 @@ export default class App {
277
279
  });
278
280
  }
279
281
  });
280
- this.post("/__authtics_host_apis__/get-is-ssr", async (req, res) => {
282
+ this.post("/__authtics_host_apis__/get-render-type", async (req, res) => {
281
283
  let datMatched = false;
282
284
  if (config_.dat && config_.dat == req.dat)
283
285
  datMatched = true;
@@ -286,7 +288,7 @@ export default class App {
286
288
  if (datMatched) {
287
289
  res.writeHead(200, { "Content-Type": "application/json" });
288
290
  res.end(JSON.stringify({
289
- isSsr: config_.ssr
291
+ renderType: config_.renderType
290
292
  }));
291
293
  }
292
294
  });
@@ -421,6 +423,7 @@ async function start() {
421
423
  catch {
422
424
  }
423
425
  const PORT = config.port ?? 3000;
426
+ const renderType = config.renderType ?? 'ssr';
424
427
  const showPanel = config.devMode !== false;
425
428
  const autoPaging = config.autoPage !== false;
426
429
  const compiler = 'esbuild';
@@ -565,7 +568,7 @@ async function start() {
565
568
  res.setHeader("Content-Type", "application/javascript");
566
569
  res.end(f);
567
570
  });
568
- }, (p) => { routes.delete(`GET:${p}`); }, req.dat, config_.dat);
571
+ }, (p) => { routes.delete(`GET:${p}`); }, req.dat, config_.dat, config_.renderType, config_.patchForSsr);
569
572
  html = html.replace("<script>", "<script>\nwindow.__paused__ = true;");
570
573
  res.setHeader("Content-Type", "text/html");
571
574
  return res.end(html);
@@ -588,7 +591,7 @@ async function start() {
588
591
  res.setHeader("Content-Type", "application/javascript");
589
592
  res.end(f);
590
593
  });
591
- }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat);
594
+ }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat, config_.renderType);
592
595
  html = html.replace("<script>", "<script>\nwindow.__paused__ = true;");
593
596
  res.setHeader("Content-Type", "text/html");
594
597
  return res.end(html);
@@ -618,7 +621,7 @@ async function start() {
618
621
  res.setHeader("Content-Type", "application/javascript");
619
622
  res.end(f);
620
623
  });
621
- }, (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);
622
625
  html = html.replace("<script>", "<script>\nwindow.__paused__ = true;");
623
626
  res.setHeader("Content-Type", "text/html");
624
627
  return res.end(html);
@@ -641,7 +644,7 @@ async function start() {
641
644
  res.setHeader("Content-Type", "application/javascript");
642
645
  res.end(f);
643
646
  });
644
- }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat);
647
+ }, (p) => { routes.delete(`GET:${p}`); }, req.cookies.dat, config_.dat, config_.renderType);
645
648
  html = html.replace("<script>", "<script>\nwindow.__paused__ = true;");
646
649
  res.setHeader("Content-Type", "text/html");
647
650
  return res.end(html);
@@ -682,6 +685,9 @@ async function start() {
682
685
  req.on('close', () => { hmrClients.delete(res); users--; });
683
686
  return;
684
687
  }
688
+ if (pathname === '/__authtics_srtws__') {
689
+ return;
690
+ }
685
691
  if (Array.isArray(config_.dat) && !config_.dat.includes(req.dat) && usersPaused) {
686
692
  if (autoPaging) {
687
693
  const pausedTsxFile = pagesDir ? path.join(pagesDir, "_paused.tsx") : undefined;
@@ -692,7 +698,7 @@ async function start() {
692
698
  res.setHeader("Content-Type", "application/javascript");
693
699
  res.end(f);
694
700
  });
695
- }, (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);
696
702
  html = html.replace("<script>", "<script>\nwindow.__paused__ = true;");
697
703
  res.setHeader("Content-Type", "text/html");
698
704
  return res.end(html);
@@ -715,7 +721,7 @@ async function start() {
715
721
  res.setHeader("Content-Type", "application/javascript");
716
722
  res.end(f);
717
723
  });
718
- }, (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);
719
725
  html = html.replace("<script>", "<script>\nwindow.__paused__ = true;");
720
726
  res.setHeader("Content-Type", "text/html");
721
727
  return res.end(html);
@@ -745,7 +751,7 @@ async function start() {
745
751
  res.setHeader("Content-Type", "application/javascript");
746
752
  res.end(f);
747
753
  });
748
- }, (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);
749
755
  html = html.replace("<script>", "<script>\nwindow.__paused__ = true;");
750
756
  res.setHeader("Content-Type", "text/html");
751
757
  return res.end(html);
@@ -768,7 +774,7 @@ async function start() {
768
774
  res.setHeader("Content-Type", "application/javascript");
769
775
  res.end(f);
770
776
  });
771
- }, (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);
772
778
  html = html.replace("<script>", "<script>\nwindow.__paused__ = true;");
773
779
  res.setHeader("Content-Type", "text/html");
774
780
  return res.end(html);
@@ -826,7 +832,7 @@ async function start() {
826
832
  res.setHeader("Content-Type", "application/javascript");
827
833
  res.end(f);
828
834
  });
829
- }, (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);
830
836
  res.setHeader("Content-Type", "text/html");
831
837
  res.writeHead(404);
832
838
  html = html.replace("<script>", "<script>\nwindow.__status_code__ = 404;");
@@ -845,6 +851,10 @@ async function start() {
845
851
  const urlPath = pathname === "/" ? "/index" : pathname;
846
852
  const tsxPath = pagesDir ? path.join(pagesDir, `${urlPath}.tsx`) : undefined;
847
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
+ }
848
858
  if (tsxPath && fs.existsSync(tsxPath)) {
849
859
  try {
850
860
  const html = await renderPage(tsxPath, url, actuallyShowPanel, compiler, hotReload, autoPaging, config.autoPageDir, (routes.get("APP_TSX") || ""), (f, p) => {
@@ -852,7 +862,7 @@ async function start() {
852
862
  res.setHeader("Content-Type", "application/javascript");
853
863
  res.end(f);
854
864
  });
855
- }, (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);
856
866
  res.setHeader("Content-Type", "text/html");
857
867
  return res.end(html);
858
868
  }
@@ -876,7 +886,7 @@ async function start() {
876
886
  res.setHeader("Content-Type", "application/javascript");
877
887
  res.end(f);
878
888
  });
879
- }, (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);
880
890
  res.setHeader("Content-Type", "text/html");
881
891
  return res.end(html);
882
892
  }
@@ -895,7 +905,7 @@ async function start() {
895
905
  res.setHeader("Content-Type", "application/javascript");
896
906
  res.end(f);
897
907
  });
898
- }, (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);
899
909
  res.setHeader("Content-Type", "text/html");
900
910
  res.writeHead(404);
901
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,7 +368,7 @@ 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);
@@ -465,15 +470,15 @@ export default function DevPanel() {
465
470
  }
466
471
  return "";
467
472
  };
468
- const isSSR = async () => {
469
- 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', {
470
475
  method: 'POST',
471
476
  headers: { 'Content-Type': 'application/json' },
472
477
  body: JSON.stringify({})
473
478
  });
474
479
  if (resp.status === 200) {
475
480
  const data = await resp.json();
476
- setIsSsr(data.isSsr);
481
+ setRenderType(data.renderType);
477
482
  }
478
483
  };
479
484
  const getUsage = async () => {
@@ -561,7 +566,7 @@ export default function DevPanel() {
561
566
  getPath();
562
567
  getCompilerType();
563
568
  getHttpVersion();
564
- isSSR();
569
+ getRenderType();
565
570
  userSettings();
566
571
  getHostVersion();
567
572
  setInterval(() => {
@@ -683,6 +688,16 @@ export default function DevPanel() {
683
688
  };
684
689
  try {
685
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
+ }
686
701
  if (existingToken) {
687
702
  setToken(existingToken);
688
703
  let length_ = existingToken?.split('');
@@ -1056,7 +1071,7 @@ export default function DevPanel() {
1056
1071
  border: '1px solid #eee',
1057
1072
  fontSize: '12px',
1058
1073
  color: '#333',
1059
- }, 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: {
1060
1075
  display: 'flex',
1061
1076
  justifyContent: 'space-between',
1062
1077
  padding: '6px 10px',
@@ -1099,7 +1114,46 @@ export default function DevPanel() {
1099
1114
  border: '1px solid #eee',
1100
1115
  fontSize: '12px',
1101
1116
  color: '#333',
1102
- }, 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: {
1103
1157
  padding: '6px 8px',
1104
1158
  borderBottom: '1px solid #ddd',
1105
1159
  background: '#fff',
@@ -1169,7 +1223,7 @@ export default function DevPanel() {
1169
1223
  border: '1px solid #eee',
1170
1224
  fontSize: '12px',
1171
1225
  color: '#333',
1172
- }, 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: () => 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: {
1173
1227
  padding: '6px 8px',
1174
1228
  borderTop: '1px solid #ddd',
1175
1229
  background: '#fff',
@@ -1229,8 +1283,8 @@ export default function DevPanel() {
1229
1283
  alignItems: 'center',
1230
1284
  padding: '8px 12px',
1231
1285
  borderRadius: hasIssue ? '100px' : '10px',
1232
- background: hasIssue ? '#ba4e4e' : '#111',
1233
- 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'}`,
1234
1288
  boxShadow: hasIssue
1235
1289
  ? '0 4px 20px rgba(220,38,38,0.2)'
1236
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.5",
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
  }