@alepha/react 0.6.0 → 0.6.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/index.browser.cjs +0 -0
- package/dist/index.browser.js +0 -0
- package/dist/index.cjs +11 -14
- package/dist/index.d.ts +2 -2
- package/dist/index.js +11 -14
- package/package.json +4 -4
- package/src/components/Link.tsx +0 -22
- package/src/components/NestedView.tsx +0 -36
- package/src/constants/SSID.ts +0 -1
- package/src/contexts/RouterContext.ts +0 -15
- package/src/contexts/RouterLayerContext.ts +0 -10
- package/src/descriptors/$auth.ts +0 -28
- package/src/descriptors/$page.ts +0 -144
- package/src/errors/RedirectionError.ts +0 -7
- package/src/hooks/RouterHookApi.ts +0 -154
- package/src/hooks/useActive.ts +0 -57
- package/src/hooks/useAuth.ts +0 -29
- package/src/hooks/useClient.ts +0 -6
- package/src/hooks/useInject.ts +0 -12
- package/src/hooks/useQueryParams.ts +0 -59
- package/src/hooks/useRouter.ts +0 -28
- package/src/hooks/useRouterEvents.ts +0 -43
- package/src/hooks/useRouterState.ts +0 -23
- package/src/index.browser.ts +0 -21
- package/src/index.shared.ts +0 -28
- package/src/index.ts +0 -46
- package/src/providers/PageDescriptorProvider.ts +0 -52
- package/src/providers/ReactAuthProvider.ts +0 -410
- package/src/providers/ReactBrowserProvider.ts +0 -250
- package/src/providers/ReactServerProvider.ts +0 -301
- package/src/services/Auth.ts +0 -45
- package/src/services/Router.ts +0 -855
|
@@ -1,301 +0,0 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
|
-
import { readFile } from "node:fs/promises";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { $logger, Alepha, type Static } from "@alepha/core";
|
|
5
|
-
import { $hook, $inject, t } from "@alepha/core";
|
|
6
|
-
import {
|
|
7
|
-
type CreateRoute,
|
|
8
|
-
type ServeDescriptorOptions,
|
|
9
|
-
ServerProvider,
|
|
10
|
-
} from "@alepha/server";
|
|
11
|
-
import { renderToString } from "react-dom/server";
|
|
12
|
-
import { $page, type PageContext } from "../descriptors/$page";
|
|
13
|
-
import { Router, type RouterRenderHelmetContext } from "../services/Router";
|
|
14
|
-
|
|
15
|
-
export const envSchema = t.object({
|
|
16
|
-
REACT_SERVER_DIST: t.string({ default: "client" }),
|
|
17
|
-
REACT_SERVER_PREFIX: t.string({ default: "" }),
|
|
18
|
-
REACT_SSR_ENABLED: t.boolean({ default: false }),
|
|
19
|
-
REACT_SSR_OUTLET: t.string({ default: "<!--ssr-outlet-->" }),
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
declare module "@alepha/core" {
|
|
23
|
-
interface Env extends Partial<Static<typeof envSchema>> {}
|
|
24
|
-
interface State {
|
|
25
|
-
"ReactServerProvider.template"?: string;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export class ReactServerProvider {
|
|
30
|
-
protected readonly log = $logger();
|
|
31
|
-
protected readonly alepha = $inject(Alepha);
|
|
32
|
-
protected readonly router = $inject(Router);
|
|
33
|
-
protected readonly server = $inject(ServerProvider);
|
|
34
|
-
protected readonly env = $inject(envSchema);
|
|
35
|
-
|
|
36
|
-
protected readonly configure = $hook({
|
|
37
|
-
name: "configure",
|
|
38
|
-
handler: async () => {
|
|
39
|
-
await this.configureRoutes();
|
|
40
|
-
},
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
protected async configureRoutes() {
|
|
44
|
-
if (this.alepha.isTest()) {
|
|
45
|
-
this.processDescriptors();
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (this.router.empty()) {
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (process.env.VITE_ALEPHA_DEV === "true") {
|
|
53
|
-
this.log.info("SSR (vite) OK");
|
|
54
|
-
const templateUrl = "http://127.0.0.1:5173/index.html"; // TODO: use env variable from vite
|
|
55
|
-
this.log.debug(`Fetch template from ${templateUrl}`);
|
|
56
|
-
|
|
57
|
-
const route = this.createHandler(() =>
|
|
58
|
-
fetch(templateUrl)
|
|
59
|
-
.then((it) => it.text())
|
|
60
|
-
.catch(() => undefined)
|
|
61
|
-
.then((it) => (it ? this.checkTemplate(it) : undefined)),
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
await this.server.route(route);
|
|
65
|
-
|
|
66
|
-
// fallback for static files
|
|
67
|
-
await this.server.route({
|
|
68
|
-
...route,
|
|
69
|
-
url: "*",
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
let root = "";
|
|
76
|
-
|
|
77
|
-
if (!this.alepha.isServerless()) {
|
|
78
|
-
const maybe = [
|
|
79
|
-
join(process.cwd(), this.env.REACT_SERVER_DIST),
|
|
80
|
-
join(process.cwd(), "..", this.env.REACT_SERVER_DIST),
|
|
81
|
-
];
|
|
82
|
-
|
|
83
|
-
for (const it of maybe) {
|
|
84
|
-
if (existsSync(it)) {
|
|
85
|
-
root = it;
|
|
86
|
-
break;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (!root) {
|
|
91
|
-
this.log.warn("Missing static files, SSR will be disabled");
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
await this.server.serve(this.createStaticHandler(root));
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const template = this.checkTemplate(
|
|
99
|
-
this.alepha.state("ReactServerProvider.template") ??
|
|
100
|
-
(await readFile(join(root, "index.html"), "utf-8")),
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
const route = this.createHandler(async () => template);
|
|
104
|
-
|
|
105
|
-
await this.server.route(route); // we must take control of "/", or it will be handled by the static handler
|
|
106
|
-
|
|
107
|
-
// fallback for static files
|
|
108
|
-
await this.server.route({
|
|
109
|
-
...route,
|
|
110
|
-
url: "*",
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Check if the template contains the outlet.
|
|
116
|
-
*
|
|
117
|
-
* @param template
|
|
118
|
-
* @protected
|
|
119
|
-
*/
|
|
120
|
-
protected checkTemplate(template: string) {
|
|
121
|
-
if (!template.includes(this.env.REACT_SSR_OUTLET)) {
|
|
122
|
-
if (!template.includes('<div id="root"></div>')) {
|
|
123
|
-
throw new Error(
|
|
124
|
-
`Missing React SSR outlet in index.html, please add ${this.env.REACT_SSR_OUTLET} to the index.html file`,
|
|
125
|
-
);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return template.replace(
|
|
129
|
-
`<div id="root"></div>`,
|
|
130
|
-
`<div id="root">${this.env.REACT_SSR_OUTLET}</div>`,
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return template;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
*
|
|
139
|
-
* @param root
|
|
140
|
-
* @protected
|
|
141
|
-
*/
|
|
142
|
-
protected createStaticHandler(root: string): ServeDescriptorOptions {
|
|
143
|
-
return {
|
|
144
|
-
root,
|
|
145
|
-
prefix: this.env.REACT_SERVER_PREFIX,
|
|
146
|
-
logLevel: "warn",
|
|
147
|
-
cacheControl: true,
|
|
148
|
-
immutable: true,
|
|
149
|
-
preCompressed: true,
|
|
150
|
-
maxAge: "30d",
|
|
151
|
-
index: false,
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
*
|
|
157
|
-
* @param templateLoader
|
|
158
|
-
* @protected
|
|
159
|
-
*/
|
|
160
|
-
protected createHandler(
|
|
161
|
-
templateLoader: () => Promise<string | undefined>,
|
|
162
|
-
): CreateRoute {
|
|
163
|
-
return {
|
|
164
|
-
method: "GET",
|
|
165
|
-
url: "/",
|
|
166
|
-
handler: async (ctx) => {
|
|
167
|
-
const template = await templateLoader();
|
|
168
|
-
if (!template) {
|
|
169
|
-
return new Response("Not found", { status: 404 });
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const response = this.notFoundHandler(ctx.url);
|
|
173
|
-
if (response) {
|
|
174
|
-
// not found handler for static files (favicon, css, js, etc)
|
|
175
|
-
return response;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return await this.ssr(ctx.url, template, {
|
|
179
|
-
user: ctx.user,
|
|
180
|
-
cookies: ctx.cookies,
|
|
181
|
-
});
|
|
182
|
-
},
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
protected processDescriptors() {
|
|
187
|
-
const pages = this.alepha.getDescriptorValues($page);
|
|
188
|
-
for (const { key, instance, value } of pages) {
|
|
189
|
-
instance[key].render = async (
|
|
190
|
-
options: {
|
|
191
|
-
params?: Record<string, string>;
|
|
192
|
-
query?: Record<string, string>;
|
|
193
|
-
} = {},
|
|
194
|
-
) => {
|
|
195
|
-
const name = value.options.name ?? key;
|
|
196
|
-
const page = this.router.page(name);
|
|
197
|
-
const layers = await this.router.createLayers(
|
|
198
|
-
"",
|
|
199
|
-
page,
|
|
200
|
-
options.params ?? {},
|
|
201
|
-
options.query ?? {},
|
|
202
|
-
[],
|
|
203
|
-
);
|
|
204
|
-
|
|
205
|
-
return renderToString(
|
|
206
|
-
this.router.root({
|
|
207
|
-
layers,
|
|
208
|
-
pathname: "",
|
|
209
|
-
search: "",
|
|
210
|
-
context: {},
|
|
211
|
-
}),
|
|
212
|
-
);
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
*
|
|
219
|
-
* @param url
|
|
220
|
-
* @protected
|
|
221
|
-
*/
|
|
222
|
-
protected notFoundHandler(url: URL) {
|
|
223
|
-
if (url.pathname.match(/\.\w+$/)) {
|
|
224
|
-
return new Response("Not found", { status: 404 });
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
*
|
|
230
|
-
* @param url
|
|
231
|
-
* @param template
|
|
232
|
-
* @param page
|
|
233
|
-
*/
|
|
234
|
-
public async ssr(
|
|
235
|
-
url: URL,
|
|
236
|
-
template: string = this.env.REACT_SSR_OUTLET,
|
|
237
|
-
page: PageContext = {},
|
|
238
|
-
): Promise<Response> {
|
|
239
|
-
const { element, layers, redirect, context } = await this.router.render(
|
|
240
|
-
url.pathname + url.search,
|
|
241
|
-
{
|
|
242
|
-
args: page,
|
|
243
|
-
},
|
|
244
|
-
);
|
|
245
|
-
|
|
246
|
-
if (redirect) {
|
|
247
|
-
return new Response("", {
|
|
248
|
-
status: 302,
|
|
249
|
-
headers: {
|
|
250
|
-
Location: redirect,
|
|
251
|
-
},
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const appHtml = renderToString(element);
|
|
256
|
-
|
|
257
|
-
const script = `<script>window.__ssr=${JSON.stringify({
|
|
258
|
-
layers: layers.map((it) => ({
|
|
259
|
-
...it,
|
|
260
|
-
index: undefined,
|
|
261
|
-
path: undefined,
|
|
262
|
-
element: undefined,
|
|
263
|
-
})),
|
|
264
|
-
})}</script>`;
|
|
265
|
-
|
|
266
|
-
const index = template.indexOf("</body>");
|
|
267
|
-
if (index !== -1) {
|
|
268
|
-
template = template.slice(0, index) + script + template.slice(index);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
if (context.helmet) {
|
|
272
|
-
template = this.renderHelmetContext(template, context.helmet);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
template = template.replace(this.env.REACT_SSR_OUTLET, appHtml);
|
|
276
|
-
|
|
277
|
-
return new Response(template, {
|
|
278
|
-
headers: { "Content-Type": "text/html" },
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
protected renderHelmetContext(
|
|
283
|
-
template: string,
|
|
284
|
-
helmetContext: RouterRenderHelmetContext,
|
|
285
|
-
) {
|
|
286
|
-
if (helmetContext.title) {
|
|
287
|
-
if (template.includes("<title>")) {
|
|
288
|
-
template = template.replace(
|
|
289
|
-
/<title>.*<\/title>/,
|
|
290
|
-
`<title>${helmetContext.title}</title>`,
|
|
291
|
-
);
|
|
292
|
-
} else {
|
|
293
|
-
template = template.replace(
|
|
294
|
-
"</head>",
|
|
295
|
-
`<title>${helmetContext.title}</title></head>`,
|
|
296
|
-
);
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
return template;
|
|
300
|
-
}
|
|
301
|
-
}
|
package/src/services/Auth.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { $hook, $inject, $logger, Alepha } from "@alepha/core";
|
|
2
|
-
import { HttpClient } from "@alepha/server";
|
|
3
|
-
import { RedirectionError } from "../errors/RedirectionError";
|
|
4
|
-
import { ReactBrowserProvider } from "../providers/ReactBrowserProvider";
|
|
5
|
-
|
|
6
|
-
export class Auth {
|
|
7
|
-
alepha = $inject(Alepha);
|
|
8
|
-
log = $logger();
|
|
9
|
-
client = $inject(HttpClient);
|
|
10
|
-
api = "/api/_oauth/login";
|
|
11
|
-
|
|
12
|
-
start = $hook({
|
|
13
|
-
name: "start",
|
|
14
|
-
handler: async () => {
|
|
15
|
-
this.client.on("onError", (err) => {
|
|
16
|
-
if (err.statusCode === 401) {
|
|
17
|
-
this.login();
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
},
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
login = (provider?: string) => {
|
|
24
|
-
if (this.alepha.isBrowser()) {
|
|
25
|
-
const browser = this.alepha.get(ReactBrowserProvider);
|
|
26
|
-
const redirect = browser.transitioning
|
|
27
|
-
? window.location.origin + browser.transitioning.to
|
|
28
|
-
: window.location.href;
|
|
29
|
-
|
|
30
|
-
window.location.href = `${this.api}?redirect=${redirect}`;
|
|
31
|
-
|
|
32
|
-
if (browser.transitioning) {
|
|
33
|
-
throw new RedirectionError(browser.state.pathname);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
throw new RedirectionError(this.api);
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
logout = () => {
|
|
43
|
-
window.location.href = `/api/_oauth/logout?redirect=${encodeURIComponent(window.location.origin)}`;
|
|
44
|
-
};
|
|
45
|
-
}
|