@alepha/react 0.12.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/chunk-DhGyd7sr.js +28 -0
- package/dist/auth/index.browser.js +394 -114
- package/dist/auth/index.browser.js.map +1 -1
- package/dist/auth/index.cjs +80 -1927
- package/dist/auth/index.cjs.map +1 -1
- package/dist/auth/index.d.cts +1130 -420
- package/dist/auth/index.d.ts +1130 -420
- package/dist/auth/index.js +72 -1918
- package/dist/auth/index.js.map +1 -1
- package/dist/core/chunk-DhGyd7sr.js +28 -0
- package/dist/core/index.browser.js +79 -79
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.cjs +89 -85
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +1654 -154
- package/dist/core/index.d.ts +1654 -154
- package/dist/core/index.js +79 -79
- package/dist/core/index.js.map +1 -1
- package/dist/form/chunk-DhGyd7sr.js +28 -0
- package/dist/form/index.cjs +28 -8
- package/dist/form/index.cjs.map +1 -1
- package/dist/form/index.d.cts +215 -7
- package/dist/form/index.d.ts +215 -7
- package/dist/form/index.js +18 -3
- package/dist/form/index.js.map +1 -1
- package/dist/head/chunk-DhGyd7sr.js +28 -0
- package/dist/head/index.browser.js +385 -59
- package/dist/head/index.browser.js.map +1 -1
- package/dist/head/index.cjs +12 -8
- package/dist/head/index.cjs.map +1 -1
- package/dist/head/index.d.cts +1230 -24
- package/dist/head/index.d.ts +1230 -29
- package/dist/head/index.js +2 -2
- package/dist/head/index.js.map +1 -1
- package/dist/i18n/chunk-DhGyd7sr.js +28 -0
- package/dist/i18n/index.cjs +33 -20
- package/dist/i18n/index.cjs.map +1 -1
- package/dist/i18n/index.d.cts +282 -13
- package/dist/i18n/index.d.ts +282 -13
- package/dist/i18n/index.js +23 -14
- package/dist/i18n/index.js.map +1 -1
- package/dist/websocket/index.cjs +21 -8
- package/dist/websocket/index.cjs.map +1 -1
- package/dist/websocket/index.js +11 -2
- package/dist/websocket/index.js.map +1 -1
- package/package.json +7 -6
- package/src/auth/index.browser.ts +3 -6
- package/src/auth/index.shared.ts +0 -1
- package/src/auth/index.ts +3 -16
- package/src/auth/providers/ReactAuthProvider.ts +1 -614
- package/src/auth/services/ReactAuth.ts +6 -17
- package/src/core/descriptors/$page.ts +1 -1
- package/src/core/index.browser.ts +1 -0
- package/src/core/index.native.ts +21 -0
- package/src/core/index.shared-router.ts +15 -0
- package/src/core/index.shared.ts +0 -14
- package/src/core/index.ts +1 -0
- package/src/core/services/ReactRouter.ts +2 -2
- package/src/form/errors/FormValidationError.ts +20 -0
- package/src/form/hooks/useForm.ts +1 -1
- package/src/form/index.ts +1 -0
- package/src/head/providers/BrowserHeadProvider.ts +1 -1
- package/src/i18n/descriptors/$dictionary.ts +7 -3
- package/src/i18n/providers/I18nProvider.ts +9 -10
- package/src/websocket/hooks/useRoom.tsx +21 -2
- package/dist/auth/index.d.cts.map +0 -1
- package/dist/auth/index.d.ts.map +0 -1
- package/dist/core/index.d.cts.map +0 -1
- package/dist/core/index.d.ts.map +0 -1
- package/dist/form/index.d.cts.map +0 -1
- package/dist/form/index.d.ts.map +0 -1
- package/dist/head/index.d.cts.map +0 -1
- package/dist/head/index.d.ts.map +0 -1
- package/dist/i18n/index.d.cts.map +0 -1
- package/dist/i18n/index.d.ts.map +0 -1
- package/dist/websocket/index.d.cts.map +0 -1
- package/dist/websocket/index.d.ts.map +0 -1
- package/src/auth/descriptors/$auth.ts +0 -436
- package/src/auth/descriptors/$authApple.ts +0 -8
- package/src/auth/descriptors/$authGithub.ts +0 -81
- package/src/auth/descriptors/$authGoogle.ts +0 -38
- package/src/auth/errors/SessionExpiredError.ts +0 -6
- package/src/auth/schemas/tokenResponseSchema.ts +0 -11
- package/src/auth/schemas/tokensSchema.ts +0 -21
- package/src/auth/schemas/userinfoResponseSchema.ts +0 -10
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
//#region rolldown:runtime
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
13
|
+
__defProp(to, key, {
|
|
14
|
+
get: ((k) => from[k]).bind(null, key),
|
|
15
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
21
|
+
};
|
|
22
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
23
|
+
value: mod,
|
|
24
|
+
enumerable: true
|
|
25
|
+
}) : target, mod));
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
export { };
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import { $atom, $env, $hook, $inject, $module, $use, Alepha, AlephaError, Atom, Descriptor, KIND, createDescriptor, t } from "alepha";
|
|
2
2
|
import { AlephaDateTime, DateTimeProvider } from "alepha/datetime";
|
|
3
|
-
import { AlephaServer, HttpClient } from "alepha/server";
|
|
4
|
-
import {
|
|
3
|
+
import { AlephaServer, HttpClient, ServerProvider, ServerRouterProvider, ServerTimingProvider } from "alepha/server";
|
|
4
|
+
import { AlephaServerCache } from "alepha/server/cache";
|
|
5
|
+
import { AlephaServerLinks, LinkProvider, ServerLinksProvider } from "alepha/server/links";
|
|
5
6
|
import { $logger } from "alepha/logger";
|
|
6
|
-
import { RouterProvider } from "alepha/router";
|
|
7
7
|
import React, { StrictMode, createContext, createElement, memo, use, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
8
8
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { existsSync } from "node:fs";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { ServerStaticProvider } from "alepha/server/static";
|
|
12
|
+
import { renderToString } from "react-dom/server";
|
|
13
|
+
import { RouterProvider } from "alepha/router";
|
|
14
|
+
import { alephaServerAuthRoutes, tokenResponseSchema, userinfoResponseSchema } from "alepha/server/auth";
|
|
11
15
|
|
|
12
16
|
//#region src/core/services/ReactPageService.ts
|
|
13
17
|
var ReactPageService = class {
|
|
@@ -67,7 +71,7 @@ var ReactPageService = class {
|
|
|
67
71
|
* const userProfile = $page({
|
|
68
72
|
* path: "/users/:id",
|
|
69
73
|
* schema: {
|
|
70
|
-
* params: t.object({ id: t.
|
|
74
|
+
* params: t.object({ id: t.integer() }),
|
|
71
75
|
* query: t.object({ tab: t.optional(t.text()) })
|
|
72
76
|
* },
|
|
73
77
|
* resolve: async ({ params }) => {
|
|
@@ -144,31 +148,6 @@ var PageDescriptor = class extends Descriptor {
|
|
|
144
148
|
};
|
|
145
149
|
$page[KIND] = PageDescriptor;
|
|
146
150
|
|
|
147
|
-
//#endregion
|
|
148
|
-
//#region src/core/components/NotFound.tsx
|
|
149
|
-
function NotFoundPage(props) {
|
|
150
|
-
return /* @__PURE__ */ jsx("div", {
|
|
151
|
-
style: {
|
|
152
|
-
height: "100vh",
|
|
153
|
-
display: "flex",
|
|
154
|
-
flexDirection: "column",
|
|
155
|
-
justifyContent: "center",
|
|
156
|
-
alignItems: "center",
|
|
157
|
-
textAlign: "center",
|
|
158
|
-
fontFamily: "sans-serif",
|
|
159
|
-
padding: "1rem",
|
|
160
|
-
...props.style
|
|
161
|
-
},
|
|
162
|
-
children: /* @__PURE__ */ jsx("h1", {
|
|
163
|
-
style: {
|
|
164
|
-
fontSize: "1rem",
|
|
165
|
-
marginBottom: "0.5rem"
|
|
166
|
-
},
|
|
167
|
-
children: "404 - This page does not exist"
|
|
168
|
-
})
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
|
|
172
151
|
//#endregion
|
|
173
152
|
//#region src/core/components/ClientOnly.tsx
|
|
174
153
|
/**
|
|
@@ -598,12 +577,37 @@ function parseAnimation(animationLike, state, type = "enter") {
|
|
|
598
577
|
}
|
|
599
578
|
}
|
|
600
579
|
|
|
580
|
+
//#endregion
|
|
581
|
+
//#region src/core/components/NotFound.tsx
|
|
582
|
+
function NotFoundPage(props) {
|
|
583
|
+
return /* @__PURE__ */ jsx("div", {
|
|
584
|
+
style: {
|
|
585
|
+
height: "100vh",
|
|
586
|
+
display: "flex",
|
|
587
|
+
flexDirection: "column",
|
|
588
|
+
justifyContent: "center",
|
|
589
|
+
alignItems: "center",
|
|
590
|
+
textAlign: "center",
|
|
591
|
+
fontFamily: "sans-serif",
|
|
592
|
+
padding: "1rem",
|
|
593
|
+
...props.style
|
|
594
|
+
},
|
|
595
|
+
children: /* @__PURE__ */ jsx("h1", {
|
|
596
|
+
style: {
|
|
597
|
+
fontSize: "1rem",
|
|
598
|
+
marginBottom: "0.5rem"
|
|
599
|
+
},
|
|
600
|
+
children: "404 - This page does not exist"
|
|
601
|
+
})
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
|
|
601
605
|
//#endregion
|
|
602
606
|
//#region src/core/providers/ReactPageProvider.ts
|
|
603
|
-
const envSchema$
|
|
607
|
+
const envSchema$2 = t.object({ REACT_STRICT_MODE: t.boolean({ default: true }) });
|
|
604
608
|
var ReactPageProvider = class {
|
|
605
609
|
log = $logger();
|
|
606
|
-
env = $env(envSchema$
|
|
610
|
+
env = $env(envSchema$2);
|
|
607
611
|
alepha = $inject(Alepha);
|
|
608
612
|
pages = [];
|
|
609
613
|
getPages() {
|
|
@@ -921,6 +925,336 @@ const isPageRoute = (it) => {
|
|
|
921
925
|
return it && typeof it === "object" && typeof it.path === "string" && typeof it.page === "object";
|
|
922
926
|
};
|
|
923
927
|
|
|
928
|
+
//#endregion
|
|
929
|
+
//#region src/core/providers/ReactServerProvider.ts
|
|
930
|
+
const envSchema$1 = t.object({
|
|
931
|
+
REACT_SSR_ENABLED: t.optional(t.boolean()),
|
|
932
|
+
REACT_ROOT_ID: t.text({ default: "root" }),
|
|
933
|
+
REACT_SERVER_TEMPLATE: t.optional(t.text({ size: "rich" }))
|
|
934
|
+
});
|
|
935
|
+
/**
|
|
936
|
+
* React server provider configuration atom
|
|
937
|
+
*/
|
|
938
|
+
const reactServerOptions = $atom({
|
|
939
|
+
name: "alepha.react.server.options",
|
|
940
|
+
schema: t.object({
|
|
941
|
+
publicDir: t.string(),
|
|
942
|
+
staticServer: t.object({
|
|
943
|
+
disabled: t.boolean(),
|
|
944
|
+
path: t.string({ description: "URL path where static files will be served." })
|
|
945
|
+
})
|
|
946
|
+
}),
|
|
947
|
+
default: {
|
|
948
|
+
publicDir: "public",
|
|
949
|
+
staticServer: {
|
|
950
|
+
disabled: false,
|
|
951
|
+
path: "/"
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
});
|
|
955
|
+
var ReactServerProvider = class {
|
|
956
|
+
log = $logger();
|
|
957
|
+
alepha = $inject(Alepha);
|
|
958
|
+
env = $env(envSchema$1);
|
|
959
|
+
pageApi = $inject(ReactPageProvider);
|
|
960
|
+
serverProvider = $inject(ServerProvider);
|
|
961
|
+
serverStaticProvider = $inject(ServerStaticProvider);
|
|
962
|
+
serverRouterProvider = $inject(ServerRouterProvider);
|
|
963
|
+
serverTimingProvider = $inject(ServerTimingProvider);
|
|
964
|
+
ROOT_DIV_REGEX = new RegExp(`<div([^>]*)\\s+id=["']${this.env.REACT_ROOT_ID}["']([^>]*)>(.*?)<\\/div>`, "is");
|
|
965
|
+
preprocessedTemplate = null;
|
|
966
|
+
options = $use(reactServerOptions);
|
|
967
|
+
/**
|
|
968
|
+
* Configure the React server provider.
|
|
969
|
+
*/
|
|
970
|
+
onConfigure = $hook({
|
|
971
|
+
on: "configure",
|
|
972
|
+
handler: async () => {
|
|
973
|
+
const ssrEnabled = this.alepha.descriptors($page).length > 0 && this.env.REACT_SSR_ENABLED !== false;
|
|
974
|
+
this.alepha.state.set("alepha.react.server.ssr", ssrEnabled);
|
|
975
|
+
if (this.alepha.isViteDev()) {
|
|
976
|
+
await this.configureVite(ssrEnabled);
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
let root = "";
|
|
980
|
+
if (!this.alepha.isServerless()) {
|
|
981
|
+
root = this.getPublicDirectory();
|
|
982
|
+
if (!root) this.log.warn("Missing static files, static file server will be disabled");
|
|
983
|
+
else {
|
|
984
|
+
this.log.debug(`Using static files from: ${root}`);
|
|
985
|
+
await this.configureStaticServer(root);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
if (ssrEnabled) {
|
|
989
|
+
await this.registerPages(async () => this.template);
|
|
990
|
+
this.log.info("SSR OK");
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
this.log.info("SSR is disabled, use History API fallback");
|
|
994
|
+
this.serverRouterProvider.createRoute({
|
|
995
|
+
path: "*",
|
|
996
|
+
handler: async ({ url, reply }) => {
|
|
997
|
+
if (url.pathname.includes(".")) {
|
|
998
|
+
reply.headers["content-type"] = "text/plain";
|
|
999
|
+
reply.body = "Not Found";
|
|
1000
|
+
reply.status = 404;
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
reply.headers["content-type"] = "text/html";
|
|
1004
|
+
return this.template;
|
|
1005
|
+
}
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
});
|
|
1009
|
+
get template() {
|
|
1010
|
+
return this.alepha.env.REACT_SERVER_TEMPLATE ?? "<!DOCTYPE html><html lang='en'><head></head><body></body></html>";
|
|
1011
|
+
}
|
|
1012
|
+
async registerPages(templateLoader) {
|
|
1013
|
+
const template = await templateLoader();
|
|
1014
|
+
if (template) this.preprocessedTemplate = this.preprocessTemplate(template);
|
|
1015
|
+
for (const page of this.pageApi.getPages()) {
|
|
1016
|
+
if (page.children?.length) continue;
|
|
1017
|
+
this.log.debug(`+ ${page.match} -> ${page.name}`);
|
|
1018
|
+
this.serverRouterProvider.createRoute({
|
|
1019
|
+
...page,
|
|
1020
|
+
schema: void 0,
|
|
1021
|
+
method: "GET",
|
|
1022
|
+
path: page.match,
|
|
1023
|
+
handler: this.createHandler(page, templateLoader)
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
/**
|
|
1028
|
+
* Get the public directory path where static files are located.
|
|
1029
|
+
*/
|
|
1030
|
+
getPublicDirectory() {
|
|
1031
|
+
const maybe = [join(process.cwd(), `dist/${this.options.publicDir}`), join(process.cwd(), this.options.publicDir)];
|
|
1032
|
+
for (const it of maybe) if (existsSync(it)) return it;
|
|
1033
|
+
return "";
|
|
1034
|
+
}
|
|
1035
|
+
/**
|
|
1036
|
+
* Configure the static file server to serve files from the given root directory.
|
|
1037
|
+
*/
|
|
1038
|
+
async configureStaticServer(root) {
|
|
1039
|
+
await this.serverStaticProvider.createStaticServer({
|
|
1040
|
+
root,
|
|
1041
|
+
cacheControl: {
|
|
1042
|
+
maxAge: 3600,
|
|
1043
|
+
immutable: true
|
|
1044
|
+
},
|
|
1045
|
+
...this.options.staticServer
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* Configure Vite for SSR.
|
|
1050
|
+
*/
|
|
1051
|
+
async configureVite(ssrEnabled) {
|
|
1052
|
+
if (!ssrEnabled) return;
|
|
1053
|
+
this.log.info("SSR (dev) OK");
|
|
1054
|
+
const url = `http://${process.env.SERVER_HOST}:${process.env.SERVER_PORT}`;
|
|
1055
|
+
await this.registerPages(() => fetch(`${url}/index.html`).then((it) => it.text()).catch(() => void 0));
|
|
1056
|
+
}
|
|
1057
|
+
/**
|
|
1058
|
+
* For testing purposes, creates a render function that can be used.
|
|
1059
|
+
*/
|
|
1060
|
+
async render(name, options = {}) {
|
|
1061
|
+
const page = this.pageApi.page(name);
|
|
1062
|
+
const url = new URL(this.pageApi.url(name, options));
|
|
1063
|
+
const state = {
|
|
1064
|
+
url,
|
|
1065
|
+
params: options.params ?? {},
|
|
1066
|
+
query: options.query ?? {},
|
|
1067
|
+
onError: () => null,
|
|
1068
|
+
layers: [],
|
|
1069
|
+
meta: {}
|
|
1070
|
+
};
|
|
1071
|
+
this.log.trace("Rendering", { url });
|
|
1072
|
+
await this.alepha.events.emit("react:server:render:begin", { state });
|
|
1073
|
+
const { redirect } = await this.pageApi.createLayers(page, state);
|
|
1074
|
+
if (redirect) return {
|
|
1075
|
+
state,
|
|
1076
|
+
html: "",
|
|
1077
|
+
redirect
|
|
1078
|
+
};
|
|
1079
|
+
if (!options.html) {
|
|
1080
|
+
this.alepha.state.set("alepha.react.router.state", state);
|
|
1081
|
+
return {
|
|
1082
|
+
state,
|
|
1083
|
+
html: renderToString(this.pageApi.root(state))
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
const template = this.template ?? "";
|
|
1087
|
+
const html = this.renderToHtml(template, state, options.hydration);
|
|
1088
|
+
if (html instanceof Redirection) return {
|
|
1089
|
+
state,
|
|
1090
|
+
html: "",
|
|
1091
|
+
redirect
|
|
1092
|
+
};
|
|
1093
|
+
const result = {
|
|
1094
|
+
state,
|
|
1095
|
+
html
|
|
1096
|
+
};
|
|
1097
|
+
await this.alepha.events.emit("react:server:render:end", result);
|
|
1098
|
+
return result;
|
|
1099
|
+
}
|
|
1100
|
+
createHandler(route, templateLoader) {
|
|
1101
|
+
return async (serverRequest) => {
|
|
1102
|
+
const { url, reply, query, params } = serverRequest;
|
|
1103
|
+
const template = await templateLoader();
|
|
1104
|
+
if (!template) throw new AlephaError("Missing template for SSR rendering");
|
|
1105
|
+
this.log.trace("Rendering page", { name: route.name });
|
|
1106
|
+
const state = {
|
|
1107
|
+
url,
|
|
1108
|
+
params,
|
|
1109
|
+
query,
|
|
1110
|
+
onError: () => null,
|
|
1111
|
+
layers: []
|
|
1112
|
+
};
|
|
1113
|
+
if (this.alepha.has(ServerLinksProvider)) this.alepha.state.set("alepha.server.request.apiLinks", await this.alepha.inject(ServerLinksProvider).getUserApiLinks({
|
|
1114
|
+
user: serverRequest.user,
|
|
1115
|
+
authorization: serverRequest.headers.authorization
|
|
1116
|
+
}));
|
|
1117
|
+
let target = route;
|
|
1118
|
+
while (target) {
|
|
1119
|
+
if (route.can && !route.can()) {
|
|
1120
|
+
reply.status = 403;
|
|
1121
|
+
reply.headers["content-type"] = "text/plain";
|
|
1122
|
+
return "Forbidden";
|
|
1123
|
+
}
|
|
1124
|
+
target = target.parent;
|
|
1125
|
+
}
|
|
1126
|
+
await this.alepha.events.emit("react:server:render:begin", {
|
|
1127
|
+
request: serverRequest,
|
|
1128
|
+
state
|
|
1129
|
+
});
|
|
1130
|
+
this.serverTimingProvider.beginTiming("createLayers");
|
|
1131
|
+
const { redirect } = await this.pageApi.createLayers(route, state);
|
|
1132
|
+
this.serverTimingProvider.endTiming("createLayers");
|
|
1133
|
+
if (redirect) return reply.redirect(redirect);
|
|
1134
|
+
reply.headers["content-type"] = "text/html";
|
|
1135
|
+
reply.headers["cache-control"] = "no-store, no-cache, must-revalidate, proxy-revalidate";
|
|
1136
|
+
reply.headers.pragma = "no-cache";
|
|
1137
|
+
reply.headers.expires = "0";
|
|
1138
|
+
const html = this.renderToHtml(template, state);
|
|
1139
|
+
if (html instanceof Redirection) {
|
|
1140
|
+
reply.redirect(typeof html.redirect === "string" ? html.redirect : this.pageApi.href(html.redirect));
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
1143
|
+
const event = {
|
|
1144
|
+
request: serverRequest,
|
|
1145
|
+
state,
|
|
1146
|
+
html
|
|
1147
|
+
};
|
|
1148
|
+
await this.alepha.events.emit("react:server:render:end", event);
|
|
1149
|
+
route.onServerResponse?.(serverRequest);
|
|
1150
|
+
this.log.trace("Page rendered", { name: route.name });
|
|
1151
|
+
return event.html;
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
renderToHtml(template, state, hydration = true) {
|
|
1155
|
+
const element = this.pageApi.root(state);
|
|
1156
|
+
this.alepha.state.set("alepha.react.router.state", state);
|
|
1157
|
+
this.serverTimingProvider.beginTiming("renderToString");
|
|
1158
|
+
let app = "";
|
|
1159
|
+
try {
|
|
1160
|
+
app = renderToString(element);
|
|
1161
|
+
} catch (error) {
|
|
1162
|
+
this.log.error("renderToString has failed, fallback to error handler", error);
|
|
1163
|
+
const element$1 = state.onError(error, state);
|
|
1164
|
+
if (element$1 instanceof Redirection) return element$1;
|
|
1165
|
+
app = renderToString(element$1);
|
|
1166
|
+
this.log.debug("Error handled successfully with fallback");
|
|
1167
|
+
}
|
|
1168
|
+
this.serverTimingProvider.endTiming("renderToString");
|
|
1169
|
+
const response = { html: template };
|
|
1170
|
+
if (hydration) {
|
|
1171
|
+
const { request, context, ...store } = this.alepha.context.als?.getStore() ?? {};
|
|
1172
|
+
const hydrationData = {
|
|
1173
|
+
...store,
|
|
1174
|
+
"alepha.react.router.state": void 0,
|
|
1175
|
+
layers: state.layers.map((it) => ({
|
|
1176
|
+
...it,
|
|
1177
|
+
error: it.error ? {
|
|
1178
|
+
...it.error,
|
|
1179
|
+
name: it.error.name,
|
|
1180
|
+
message: it.error.message,
|
|
1181
|
+
stack: !this.alepha.isProduction() ? it.error.stack : void 0
|
|
1182
|
+
} : void 0,
|
|
1183
|
+
index: void 0,
|
|
1184
|
+
path: void 0,
|
|
1185
|
+
element: void 0,
|
|
1186
|
+
route: void 0
|
|
1187
|
+
}))
|
|
1188
|
+
};
|
|
1189
|
+
const script = `<script>window.__ssr=${JSON.stringify(hydrationData)}<\/script>`;
|
|
1190
|
+
this.fillTemplate(response, app, script);
|
|
1191
|
+
}
|
|
1192
|
+
return response.html;
|
|
1193
|
+
}
|
|
1194
|
+
preprocessTemplate(template) {
|
|
1195
|
+
const bodyCloseIndex = template.match(/<\/body>/i)?.index ?? template.length;
|
|
1196
|
+
const beforeScript = template.substring(0, bodyCloseIndex);
|
|
1197
|
+
const afterScript = template.substring(bodyCloseIndex);
|
|
1198
|
+
const rootDivMatch = beforeScript.match(this.ROOT_DIV_REGEX);
|
|
1199
|
+
if (rootDivMatch) {
|
|
1200
|
+
const beforeDiv = beforeScript.substring(0, rootDivMatch.index);
|
|
1201
|
+
const afterDivStart = rootDivMatch.index + rootDivMatch[0].length;
|
|
1202
|
+
const afterDiv = beforeScript.substring(afterDivStart);
|
|
1203
|
+
return {
|
|
1204
|
+
beforeApp: `${beforeDiv}<div${rootDivMatch[1]} id="${this.env.REACT_ROOT_ID}"${rootDivMatch[2]}>`,
|
|
1205
|
+
afterApp: `</div>${afterDiv}`,
|
|
1206
|
+
beforeScript: "",
|
|
1207
|
+
afterScript
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
const bodyMatch = beforeScript.match(/<body([^>]*)>/i);
|
|
1211
|
+
if (bodyMatch) {
|
|
1212
|
+
const beforeBody = beforeScript.substring(0, bodyMatch.index + bodyMatch[0].length);
|
|
1213
|
+
const afterBody = beforeScript.substring(bodyMatch.index + bodyMatch[0].length);
|
|
1214
|
+
return {
|
|
1215
|
+
beforeApp: `${beforeBody}<div id="${this.env.REACT_ROOT_ID}">`,
|
|
1216
|
+
afterApp: `</div>${afterBody}`,
|
|
1217
|
+
beforeScript: "",
|
|
1218
|
+
afterScript
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1221
|
+
return {
|
|
1222
|
+
beforeApp: `<div id="${this.env.REACT_ROOT_ID}">`,
|
|
1223
|
+
afterApp: `</div>`,
|
|
1224
|
+
beforeScript,
|
|
1225
|
+
afterScript
|
|
1226
|
+
};
|
|
1227
|
+
}
|
|
1228
|
+
fillTemplate(response, app, script) {
|
|
1229
|
+
if (!this.preprocessedTemplate) this.preprocessedTemplate = this.preprocessTemplate(response.html);
|
|
1230
|
+
response.html = this.preprocessedTemplate.beforeApp + app + this.preprocessedTemplate.afterApp + script + this.preprocessedTemplate.afterScript;
|
|
1231
|
+
}
|
|
1232
|
+
};
|
|
1233
|
+
|
|
1234
|
+
//#endregion
|
|
1235
|
+
//#region src/core/services/ReactPageServerService.ts
|
|
1236
|
+
var ReactPageServerService = class extends ReactPageService {
|
|
1237
|
+
reactServerProvider = $inject(ReactServerProvider);
|
|
1238
|
+
serverProvider = $inject(ServerProvider);
|
|
1239
|
+
async render(name, options = {}) {
|
|
1240
|
+
return this.reactServerProvider.render(name, options);
|
|
1241
|
+
}
|
|
1242
|
+
async fetch(pathname, options = {}) {
|
|
1243
|
+
const response = await fetch(`${this.serverProvider.hostname}/${pathname}`);
|
|
1244
|
+
const html = await response.text();
|
|
1245
|
+
if (options?.html) return {
|
|
1246
|
+
html,
|
|
1247
|
+
response
|
|
1248
|
+
};
|
|
1249
|
+
const match = html.match(this.reactServerProvider.ROOT_DIV_REGEX);
|
|
1250
|
+
if (match) return {
|
|
1251
|
+
html: match[3],
|
|
1252
|
+
response
|
|
1253
|
+
};
|
|
1254
|
+
throw new AlephaError("Invalid HTML response");
|
|
1255
|
+
}
|
|
1256
|
+
};
|
|
1257
|
+
|
|
924
1258
|
//#endregion
|
|
925
1259
|
//#region src/core/providers/ReactBrowserRouterProvider.ts
|
|
926
1260
|
var ReactBrowserRouterProvider = class extends RouterProvider {
|
|
@@ -1167,26 +1501,6 @@ var ReactBrowserProvider = class {
|
|
|
1167
1501
|
});
|
|
1168
1502
|
};
|
|
1169
1503
|
|
|
1170
|
-
//#endregion
|
|
1171
|
-
//#region src/core/providers/ReactBrowserRendererProvider.ts
|
|
1172
|
-
var ReactBrowserRendererProvider = class {
|
|
1173
|
-
log = $logger();
|
|
1174
|
-
root;
|
|
1175
|
-
onBrowserRender = $hook({
|
|
1176
|
-
on: "react:browser:render",
|
|
1177
|
-
handler: async ({ hydration, root, element }) => {
|
|
1178
|
-
if (hydration?.layers) {
|
|
1179
|
-
this.root = hydrateRoot(root, element);
|
|
1180
|
-
this.log.info("Hydrated root element");
|
|
1181
|
-
} else {
|
|
1182
|
-
this.root ??= createRoot(root);
|
|
1183
|
-
this.root.render(element);
|
|
1184
|
-
this.log.info("Created root element");
|
|
1185
|
-
}
|
|
1186
|
-
}
|
|
1187
|
-
});
|
|
1188
|
-
};
|
|
1189
|
-
|
|
1190
1504
|
//#endregion
|
|
1191
1505
|
//#region src/core/services/ReactRouter.ts
|
|
1192
1506
|
var ReactRouter = class {
|
|
@@ -1213,7 +1527,7 @@ var ReactRouter = class {
|
|
|
1213
1527
|
path(name, config = {}) {
|
|
1214
1528
|
return this.pageApi.pathname(name, {
|
|
1215
1529
|
params: {
|
|
1216
|
-
...this.state
|
|
1530
|
+
...this.state?.params,
|
|
1217
1531
|
...config.params
|
|
1218
1532
|
},
|
|
1219
1533
|
query: config.query
|
|
@@ -1301,47 +1615,31 @@ var ReactRouter = class {
|
|
|
1301
1615
|
};
|
|
1302
1616
|
|
|
1303
1617
|
//#endregion
|
|
1304
|
-
//#region src/core/index.
|
|
1618
|
+
//#region src/core/index.ts
|
|
1619
|
+
/**
|
|
1620
|
+
* Provides full-stack React development with declarative routing, server-side rendering, and client-side hydration.
|
|
1621
|
+
*
|
|
1622
|
+
* The React module enables building modern React applications using the `$page` descriptor on class properties.
|
|
1623
|
+
* It delivers seamless server-side rendering, automatic code splitting, and client-side navigation with full
|
|
1624
|
+
* type safety and schema validation for route parameters and data.
|
|
1625
|
+
*
|
|
1626
|
+
* @see {@link $page}
|
|
1627
|
+
* @module alepha.react
|
|
1628
|
+
*/
|
|
1305
1629
|
const AlephaReact = $module({
|
|
1306
1630
|
name: "alepha.react",
|
|
1307
1631
|
descriptors: [$page],
|
|
1308
1632
|
services: [
|
|
1633
|
+
ReactServerProvider,
|
|
1309
1634
|
ReactPageProvider,
|
|
1310
|
-
ReactBrowserRouterProvider,
|
|
1311
|
-
ReactBrowserProvider,
|
|
1312
1635
|
ReactRouter,
|
|
1313
|
-
|
|
1314
|
-
|
|
1636
|
+
ReactPageService,
|
|
1637
|
+
ReactPageServerService
|
|
1315
1638
|
],
|
|
1316
|
-
register: (alepha) => alepha.with(AlephaDateTime).with(AlephaServer).with(
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
//#region src/auth/schemas/tokensSchema.ts
|
|
1321
|
-
const tokensSchema = t.object({
|
|
1322
|
-
provider: t.text(),
|
|
1323
|
-
access_token: t.text({ size: "rich" }),
|
|
1324
|
-
issued_at: t.number(),
|
|
1325
|
-
expires_in: t.optional(t.number()),
|
|
1326
|
-
refresh_token: t.optional(t.text({ size: "rich" })),
|
|
1327
|
-
refresh_token_expires_in: t.optional(t.number()),
|
|
1328
|
-
refresh_expires_in: t.optional(t.number({ description: "Alias of `refresh_token_expires_in` for compatibility with some providers." })),
|
|
1329
|
-
id_token: t.optional(t.text({ size: "rich" })),
|
|
1330
|
-
scope: t.optional(t.text())
|
|
1331
|
-
});
|
|
1332
|
-
|
|
1333
|
-
//#endregion
|
|
1334
|
-
//#region src/auth/schemas/tokenResponseSchema.ts
|
|
1335
|
-
const tokenResponseSchema = t.extend(tokensSchema, {
|
|
1336
|
-
user: userAccountInfoSchema,
|
|
1337
|
-
api: apiLinksResponseSchema
|
|
1338
|
-
});
|
|
1339
|
-
|
|
1340
|
-
//#endregion
|
|
1341
|
-
//#region src/auth/schemas/userinfoResponseSchema.ts
|
|
1342
|
-
const userinfoResponseSchema = t.object({
|
|
1343
|
-
user: t.optional(userAccountInfoSchema),
|
|
1344
|
-
api: apiLinksResponseSchema
|
|
1639
|
+
register: (alepha) => alepha.with(AlephaDateTime).with(AlephaServer).with(AlephaServerCache).with(AlephaServerLinks).with({
|
|
1640
|
+
provide: ReactPageService,
|
|
1641
|
+
use: ReactPageServerService
|
|
1642
|
+
}).with(ReactServerProvider).with(ReactPageProvider).with(ReactRouter)
|
|
1345
1643
|
});
|
|
1346
1644
|
|
|
1347
1645
|
//#endregion
|
|
@@ -1349,18 +1647,10 @@ const userinfoResponseSchema = t.object({
|
|
|
1349
1647
|
/**
|
|
1350
1648
|
* Browser, SSR friendly, service to handle authentication.
|
|
1351
1649
|
*/
|
|
1352
|
-
var ReactAuth = class
|
|
1650
|
+
var ReactAuth = class {
|
|
1353
1651
|
log = $logger();
|
|
1354
1652
|
alepha = $inject(Alepha);
|
|
1355
1653
|
httpClient = $inject(HttpClient);
|
|
1356
|
-
static path = {
|
|
1357
|
-
login: "/oauth/login",
|
|
1358
|
-
callback: "/oauth/callback",
|
|
1359
|
-
logout: "/oauth/logout",
|
|
1360
|
-
token: "/_auth/token",
|
|
1361
|
-
refresh: "/_auth/refresh",
|
|
1362
|
-
userinfo: "/_auth/userinfo"
|
|
1363
|
-
};
|
|
1364
1654
|
onBeginTransition = $hook({
|
|
1365
1655
|
on: "react:transition:begin",
|
|
1366
1656
|
handler: async (event) => {
|
|
@@ -1382,14 +1672,14 @@ var ReactAuth = class ReactAuth {
|
|
|
1382
1672
|
return this.alepha.state.get("alepha.server.request.user");
|
|
1383
1673
|
}
|
|
1384
1674
|
async ping() {
|
|
1385
|
-
const { data } = await this.httpClient.fetch(
|
|
1675
|
+
const { data } = await this.httpClient.fetch(alephaServerAuthRoutes.userinfo, { schema: { response: userinfoResponseSchema } });
|
|
1386
1676
|
this.alepha.state.set("alepha.server.request.apiLinks", data.api);
|
|
1387
1677
|
this.alepha.state.set("alepha.server.request.user", data.user);
|
|
1388
1678
|
return data.user;
|
|
1389
1679
|
}
|
|
1390
1680
|
async login(provider, options) {
|
|
1391
1681
|
if (options.username || options.password) {
|
|
1392
|
-
const { data } = await this.httpClient.fetch(`${options.hostname || ""}${
|
|
1682
|
+
const { data } = await this.httpClient.fetch(`${options.hostname || ""}${alephaServerAuthRoutes.token}?provider=${provider}`, {
|
|
1393
1683
|
method: "POST",
|
|
1394
1684
|
body: JSON.stringify({
|
|
1395
1685
|
username: options.username,
|
|
@@ -1405,27 +1695,20 @@ var ReactAuth = class ReactAuth {
|
|
|
1405
1695
|
if (this.alepha.isBrowser()) {
|
|
1406
1696
|
const browser = this.alepha.inject(ReactBrowserProvider);
|
|
1407
1697
|
const redirect = options.redirect || (browser.transitioning ? window.location.origin + browser.transitioning.to : window.location.href);
|
|
1408
|
-
const href = `${window.location.origin}${
|
|
1698
|
+
const href = `${window.location.origin}${alephaServerAuthRoutes.login}?provider=${provider}&redirect_uri=${encodeURIComponent(redirect)}`;
|
|
1409
1699
|
if (browser.transitioning) throw new Redirection(href);
|
|
1410
1700
|
else {
|
|
1411
1701
|
window.location.href = href;
|
|
1412
1702
|
return {};
|
|
1413
1703
|
}
|
|
1414
1704
|
}
|
|
1415
|
-
throw new Redirection(`${
|
|
1705
|
+
throw new Redirection(`${alephaServerAuthRoutes.login}?provider=${provider}&redirect_uri=${options.redirect || "/"}`);
|
|
1416
1706
|
}
|
|
1417
1707
|
logout() {
|
|
1418
|
-
window.location.href = `${
|
|
1708
|
+
window.location.href = `${alephaServerAuthRoutes.logout}?post_logout_redirect_uri=${encodeURIComponent(window.location.origin)}`;
|
|
1419
1709
|
}
|
|
1420
1710
|
};
|
|
1421
1711
|
|
|
1422
|
-
//#endregion
|
|
1423
|
-
//#region src/auth/errors/SessionExpiredError.ts
|
|
1424
|
-
var SessionExpiredError = class extends AlephaError {
|
|
1425
|
-
name = "SessionExpiredError";
|
|
1426
|
-
status = 401;
|
|
1427
|
-
};
|
|
1428
|
-
|
|
1429
1712
|
//#endregion
|
|
1430
1713
|
//#region src/auth/hooks/useAuth.ts
|
|
1431
1714
|
const useAuth = () => {
|
|
@@ -1449,12 +1732,9 @@ const useAuth = () => {
|
|
|
1449
1732
|
//#region src/auth/index.browser.ts
|
|
1450
1733
|
const AlephaReactAuth = $module({
|
|
1451
1734
|
name: "alepha.react.auth",
|
|
1452
|
-
|
|
1453
|
-
register: (alepha) => {
|
|
1454
|
-
alepha.with(ReactAuth);
|
|
1455
|
-
}
|
|
1735
|
+
services: [ReactAuth]
|
|
1456
1736
|
});
|
|
1457
1737
|
|
|
1458
1738
|
//#endregion
|
|
1459
|
-
export { AlephaReactAuth, ReactAuth,
|
|
1739
|
+
export { AlephaReactAuth, ReactAuth, useAuth };
|
|
1460
1740
|
//# sourceMappingURL=index.browser.js.map
|