@alepha/react 0.6.1 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -28
- package/dist/index.browser.cjs +19 -23
- package/dist/index.browser.js +7 -7
- package/dist/index.cjs +235 -512
- package/dist/index.d.ts +240 -678
- package/dist/index.js +220 -492
- package/dist/{useAuth-DOVx2kqa.cjs → useActive-BVqdq757.cjs} +333 -431
- package/dist/{useAuth-i7wbKVrt.js → useActive-dAmCT31a.js} +332 -427
- package/package.json +13 -14
package/dist/index.cjs
CHANGED
|
@@ -2,575 +2,298 @@
|
|
|
2
2
|
|
|
3
3
|
var core = require('@alepha/core');
|
|
4
4
|
var server = require('@alepha/server');
|
|
5
|
-
var
|
|
6
|
-
var openidClient = require('openid-client');
|
|
5
|
+
var useActive = require('./useActive-BVqdq757.cjs');
|
|
7
6
|
var node_fs = require('node:fs');
|
|
8
7
|
var promises = require('node:fs/promises');
|
|
9
8
|
var node_path = require('node:path');
|
|
9
|
+
var serverStatic = require('@alepha/server-static');
|
|
10
10
|
var server$1 = require('react-dom/server');
|
|
11
11
|
require('react/jsx-runtime');
|
|
12
12
|
require('react');
|
|
13
13
|
require('react-dom/client');
|
|
14
|
-
require('
|
|
14
|
+
require('@alepha/router');
|
|
15
15
|
|
|
16
|
-
class
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
schema: core.t.object({
|
|
26
|
-
codeVerifier: core.t.optional(core.t.string({ size: "long" })),
|
|
27
|
-
redirectUri: core.t.optional(core.t.string({ size: "long" }))
|
|
28
|
-
})
|
|
29
|
-
});
|
|
30
|
-
tokens = server.$cookie({
|
|
31
|
-
name: "tokens",
|
|
32
|
-
ttl: { days: 1 },
|
|
33
|
-
httpOnly: true,
|
|
34
|
-
compress: true,
|
|
35
|
-
schema: core.t.object({
|
|
36
|
-
access_token: core.t.optional(core.t.string({ size: "rich" })),
|
|
37
|
-
expires_in: core.t.optional(core.t.number()),
|
|
38
|
-
refresh_token: core.t.optional(core.t.string({ size: "rich" })),
|
|
39
|
-
id_token: core.t.optional(core.t.string({ size: "rich" })),
|
|
40
|
-
scope: core.t.optional(core.t.string()),
|
|
41
|
-
issued_at: core.t.optional(core.t.number())
|
|
42
|
-
})
|
|
43
|
-
});
|
|
44
|
-
user = server.$cookie({
|
|
45
|
-
name: "user",
|
|
46
|
-
ttl: { days: 1 },
|
|
47
|
-
schema: core.t.object({
|
|
48
|
-
id: core.t.string(),
|
|
49
|
-
name: core.t.optional(core.t.string()),
|
|
50
|
-
email: core.t.optional(core.t.string())
|
|
51
|
-
})
|
|
52
|
-
});
|
|
53
|
-
configure = core.$hook({
|
|
54
|
-
name: "configure",
|
|
55
|
-
handler: async () => {
|
|
56
|
-
const auths = this.alepha.getDescriptorValues(useAuth.$auth);
|
|
57
|
-
for (const auth of auths) {
|
|
58
|
-
const options = auth.value.options;
|
|
59
|
-
if (options.oidc) {
|
|
60
|
-
this.authProviders.push({
|
|
61
|
-
name: options.name ?? auth.key,
|
|
62
|
-
redirectUri: options.oidc.redirectUri ?? "/api/_oauth/callback",
|
|
63
|
-
client: await openidClient.discovery(
|
|
64
|
-
new URL(options.oidc.issuer),
|
|
65
|
-
options.oidc.clientId,
|
|
66
|
-
{
|
|
67
|
-
client_secret: options.oidc.clientSecret
|
|
68
|
-
},
|
|
69
|
-
void 0,
|
|
70
|
-
{
|
|
71
|
-
execute: [openidClient.allowInsecureRequests]
|
|
72
|
-
}
|
|
73
|
-
)
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
/**
|
|
80
|
-
* Configure Fastify to forward Session Access Token to Header Authorization.
|
|
81
|
-
*/
|
|
82
|
-
configureFastify = core.$hook({
|
|
83
|
-
name: "configure:fastify",
|
|
84
|
-
after: this.fastifyCookieProvider,
|
|
85
|
-
handler: async (app) => {
|
|
86
|
-
app.addHook("onRequest", async (req) => {
|
|
87
|
-
if (req.cookies && !this.isViteFile(req.url) && !!this.authProviders.length) {
|
|
88
|
-
const tokens = await this.refresh(req.cookies);
|
|
89
|
-
if (tokens) {
|
|
90
|
-
req.headers.authorization = `Bearer ${tokens.access_token}`;
|
|
91
|
-
}
|
|
92
|
-
if (this.user.get(req.cookies) && !this.tokens.get(req.cookies)) {
|
|
93
|
-
this.user.del(req.cookies);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
/**
|
|
100
|
-
*
|
|
101
|
-
* @param cookies
|
|
102
|
-
* @protected
|
|
103
|
-
*/
|
|
104
|
-
async refresh(cookies) {
|
|
105
|
-
const now = Date.now();
|
|
106
|
-
const tokens = this.tokens.get(cookies);
|
|
107
|
-
if (!tokens) {
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
if (tokens.expires_in && tokens.issued_at) {
|
|
111
|
-
const expiresAt = tokens.issued_at + (tokens.expires_in - 10) * 1e3;
|
|
112
|
-
if (expiresAt < now) {
|
|
113
|
-
if (tokens.refresh_token) {
|
|
114
|
-
try {
|
|
115
|
-
const newTokens = await openidClient.refreshTokenGrant(
|
|
116
|
-
this.authProviders[0].client,
|
|
117
|
-
tokens.refresh_token
|
|
118
|
-
);
|
|
119
|
-
this.tokens.set(cookies, {
|
|
120
|
-
...newTokens,
|
|
121
|
-
issued_at: Date.now()
|
|
122
|
-
});
|
|
123
|
-
return newTokens;
|
|
124
|
-
} catch (e) {
|
|
125
|
-
this.log.warn(e, "Failed to refresh token -");
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
this.tokens.del(cookies);
|
|
129
|
-
this.user.del(cookies);
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
if (!tokens.issued_at && tokens.access_token) {
|
|
134
|
-
this.tokens.del(cookies);
|
|
135
|
-
this.user.del(cookies);
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
return tokens;
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
*
|
|
142
|
-
*/
|
|
143
|
-
login = server.$route({
|
|
144
|
-
security: false,
|
|
145
|
-
internal: true,
|
|
146
|
-
url: "/_oauth/login",
|
|
147
|
-
group: "auth",
|
|
148
|
-
method: "GET",
|
|
149
|
-
schema: {
|
|
150
|
-
query: core.t.object({
|
|
151
|
-
redirect: core.t.optional(core.t.string()),
|
|
152
|
-
provider: core.t.optional(core.t.string())
|
|
153
|
-
})
|
|
154
|
-
},
|
|
155
|
-
handler: async ({ query, cookies, url }) => {
|
|
156
|
-
const { client, redirectUri } = this.provider(query.provider);
|
|
157
|
-
const codeVerifier = openidClient.randomPKCECodeVerifier();
|
|
158
|
-
const codeChallenge = await openidClient.calculatePKCECodeChallenge(codeVerifier);
|
|
159
|
-
const scope = "openid profile email";
|
|
160
|
-
let redirect_uri = redirectUri;
|
|
161
|
-
if (redirect_uri.startsWith("/")) {
|
|
162
|
-
redirect_uri = `${url.protocol}//${url.host}${redirect_uri}`;
|
|
163
|
-
}
|
|
164
|
-
const parameters = {
|
|
165
|
-
redirect_uri,
|
|
166
|
-
scope,
|
|
167
|
-
code_challenge: codeChallenge,
|
|
168
|
-
code_challenge_method: "S256"
|
|
169
|
-
};
|
|
170
|
-
this.authorizationCode.set(cookies, {
|
|
171
|
-
codeVerifier,
|
|
172
|
-
redirectUri: query.redirect ?? "/"
|
|
173
|
-
});
|
|
174
|
-
return new Response("", {
|
|
175
|
-
status: 302,
|
|
176
|
-
headers: {
|
|
177
|
-
Location: openidClient.buildAuthorizationUrl(client, parameters).toString()
|
|
178
|
-
}
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
});
|
|
182
|
-
/**
|
|
183
|
-
*
|
|
184
|
-
*/
|
|
185
|
-
callback = server.$route({
|
|
186
|
-
security: false,
|
|
187
|
-
internal: true,
|
|
188
|
-
url: "/_oauth/callback",
|
|
189
|
-
group: "auth",
|
|
190
|
-
method: "GET",
|
|
191
|
-
schema: {
|
|
192
|
-
query: core.t.object({
|
|
193
|
-
provider: core.t.optional(core.t.string())
|
|
194
|
-
})
|
|
195
|
-
},
|
|
196
|
-
handler: async ({ url, cookies, query }) => {
|
|
197
|
-
const { client } = this.provider(query.provider);
|
|
198
|
-
const authorizationCode = this.authorizationCode.get(cookies);
|
|
199
|
-
if (!authorizationCode) {
|
|
200
|
-
throw new server.BadRequestError("Missing code verifier");
|
|
201
|
-
}
|
|
202
|
-
const tokens = await openidClient.authorizationCodeGrant(client, url, {
|
|
203
|
-
pkceCodeVerifier: authorizationCode.codeVerifier
|
|
204
|
-
});
|
|
205
|
-
this.authorizationCode.del(cookies);
|
|
206
|
-
this.tokens.set(cookies, {
|
|
207
|
-
...tokens,
|
|
208
|
-
issued_at: Date.now()
|
|
209
|
-
});
|
|
210
|
-
const user = this.userFromAccessToken(tokens.access_token);
|
|
211
|
-
if (user) {
|
|
212
|
-
this.user.set(cookies, user);
|
|
213
|
-
}
|
|
214
|
-
return Response.redirect(authorizationCode.redirectUri ?? "/");
|
|
16
|
+
class ServerHeadProvider {
|
|
17
|
+
renderHead(template, head) {
|
|
18
|
+
let result = template;
|
|
19
|
+
const htmlAttributes = head.htmlAttributes;
|
|
20
|
+
if (htmlAttributes) {
|
|
21
|
+
result = result.replace(
|
|
22
|
+
/<html([^>]*)>/i,
|
|
23
|
+
(_, existingAttrs) => `<html${this.mergeAttributes(existingAttrs, htmlAttributes)}>`
|
|
24
|
+
);
|
|
215
25
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
userFromAccessToken(accessToken) {
|
|
223
|
-
try {
|
|
224
|
-
const parts = accessToken.split(".");
|
|
225
|
-
if (parts.length !== 3) {
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
const payload = parts[1];
|
|
229
|
-
const decoded = JSON.parse(atob(payload));
|
|
230
|
-
if (!decoded.sub) {
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
233
|
-
return {
|
|
234
|
-
id: decoded.sub,
|
|
235
|
-
name: decoded.name,
|
|
236
|
-
email: decoded.email
|
|
237
|
-
// organization
|
|
238
|
-
// ...
|
|
239
|
-
};
|
|
240
|
-
} catch (e) {
|
|
241
|
-
this.log.warn(e, "Failed to decode access token");
|
|
26
|
+
const bodyAttributes = head.bodyAttributes;
|
|
27
|
+
if (bodyAttributes) {
|
|
28
|
+
result = result.replace(
|
|
29
|
+
/<body([^>]*)>/i,
|
|
30
|
+
(_, existingAttrs) => `<body${this.mergeAttributes(existingAttrs, bodyAttributes)}>`
|
|
31
|
+
);
|
|
242
32
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
query: core.t.object({
|
|
255
|
-
redirect: core.t.optional(core.t.string()),
|
|
256
|
-
provider: core.t.optional(core.t.string())
|
|
257
|
-
})
|
|
258
|
-
},
|
|
259
|
-
handler: async ({ query, cookies }) => {
|
|
260
|
-
const { client } = this.provider(query.provider);
|
|
261
|
-
const tokens = this.tokens.get(cookies);
|
|
262
|
-
const idToken = tokens?.id_token;
|
|
263
|
-
const redirect = query.redirect ?? "/";
|
|
264
|
-
const params = new URLSearchParams();
|
|
265
|
-
params.set("post_logout_redirect_uri", redirect);
|
|
266
|
-
if (idToken) {
|
|
267
|
-
params.set("id_token_hint", idToken);
|
|
33
|
+
let headContent = "";
|
|
34
|
+
const title = head.title;
|
|
35
|
+
if (title) {
|
|
36
|
+
if (template.includes("<title>")) {
|
|
37
|
+
result = result.replace(
|
|
38
|
+
/<title>(.*?)<\/title>/i,
|
|
39
|
+
() => `<title>${this.escapeHtml(title)}</title>`
|
|
40
|
+
);
|
|
41
|
+
} else {
|
|
42
|
+
headContent += `<title>${this.escapeHtml(title)}</title>
|
|
43
|
+
`;
|
|
268
44
|
}
|
|
269
|
-
this.tokens.del(cookies);
|
|
270
|
-
this.user.del(cookies);
|
|
271
|
-
return Response.redirect(openidClient.buildEndSessionUrl(client, params).toString());
|
|
272
45
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
* @protected
|
|
278
|
-
*/
|
|
279
|
-
provider(name) {
|
|
280
|
-
if (!name) {
|
|
281
|
-
const client = this.authProviders[0];
|
|
282
|
-
if (!client) {
|
|
283
|
-
throw new server.BadRequestError("Client name is required");
|
|
46
|
+
if (head.meta) {
|
|
47
|
+
for (const meta of head.meta) {
|
|
48
|
+
headContent += `<meta name="${this.escapeHtml(meta.name)}" content="${this.escapeHtml(meta.content)}">
|
|
49
|
+
`;
|
|
284
50
|
}
|
|
285
|
-
return client;
|
|
286
51
|
}
|
|
287
|
-
|
|
288
|
-
(
|
|
52
|
+
result = result.replace(
|
|
53
|
+
/<head([^>]*)>(.*?)<\/head>/is,
|
|
54
|
+
(_, existingAttrs, existingHead) => `<head${existingAttrs}>${existingHead}${headContent}</head>`
|
|
289
55
|
);
|
|
290
|
-
|
|
291
|
-
throw new server.BadRequestError(`Client ${name} not found`);
|
|
292
|
-
}
|
|
293
|
-
return authProvider;
|
|
56
|
+
return result.trim();
|
|
294
57
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}
|
|
308
|
-
if (pathname.startsWith("/@")) {
|
|
309
|
-
return true;
|
|
58
|
+
mergeAttributes(existing, attrs) {
|
|
59
|
+
const existingAttrs = this.parseAttributes(existing);
|
|
60
|
+
const merged = { ...existingAttrs, ...attrs };
|
|
61
|
+
return Object.entries(merged).map(([k, v]) => ` ${k}="${this.escapeHtml(v)}"`).join("");
|
|
62
|
+
}
|
|
63
|
+
parseAttributes(attrStr) {
|
|
64
|
+
const attrs = {};
|
|
65
|
+
const attrRegex = /([^\s=]+)(?:="([^"]*)")?/g;
|
|
66
|
+
let match = attrRegex.exec(attrStr);
|
|
67
|
+
while (match) {
|
|
68
|
+
attrs[match[1]] = match[2] ?? "";
|
|
69
|
+
match = attrRegex.exec(attrStr);
|
|
310
70
|
}
|
|
311
|
-
return
|
|
71
|
+
return attrs;
|
|
72
|
+
}
|
|
73
|
+
escapeHtml(str) {
|
|
74
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
312
75
|
}
|
|
313
76
|
}
|
|
314
77
|
|
|
315
|
-
const envSchema
|
|
78
|
+
const envSchema = core.t.object({
|
|
316
79
|
REACT_SERVER_DIST: core.t.string({ default: "client" }),
|
|
317
80
|
REACT_SERVER_PREFIX: core.t.string({ default: "" }),
|
|
318
81
|
REACT_SSR_ENABLED: core.t.boolean({ default: false }),
|
|
319
|
-
|
|
82
|
+
REACT_ROOT_ID: core.t.string({ default: "root" })
|
|
320
83
|
});
|
|
321
84
|
class ReactServerProvider {
|
|
322
85
|
log = core.$logger();
|
|
323
86
|
alepha = core.$inject(core.Alepha);
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
87
|
+
pageDescriptorProvider = core.$inject(useActive.PageDescriptorProvider);
|
|
88
|
+
serverStaticProvider = core.$inject(serverStatic.ServerStaticProvider);
|
|
89
|
+
serverRouterProvider = core.$inject(server.ServerRouterProvider);
|
|
90
|
+
headProvider = core.$inject(ServerHeadProvider);
|
|
91
|
+
env = core.$inject(envSchema);
|
|
92
|
+
ROOT_DIV_REGEX = new RegExp(
|
|
93
|
+
`<div([^>]*)\\s+id=["']${this.env.REACT_ROOT_ID}["']([^>]*)>(.*?)<\\/div>`,
|
|
94
|
+
"is"
|
|
95
|
+
);
|
|
327
96
|
configure = core.$hook({
|
|
328
97
|
name: "configure",
|
|
329
98
|
handler: async () => {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
return;
|
|
339
|
-
}
|
|
340
|
-
if (process.env.VITE_ALEPHA_DEV === "true") {
|
|
341
|
-
this.log.info("SSR (vite) OK");
|
|
342
|
-
const templateUrl = `http://${process.env.SERVER_HOST}:${process.env.SERVER_PORT}/index.html`;
|
|
343
|
-
this.log.debug(`Fetch template from ${templateUrl}`);
|
|
344
|
-
const route2 = this.createHandler(
|
|
345
|
-
() => fetch(templateUrl).then((it) => it.text()).catch(() => void 0).then((it) => it ? this.checkTemplate(it) : void 0)
|
|
346
|
-
);
|
|
347
|
-
await this.server.route(route2);
|
|
348
|
-
await this.server.route({
|
|
349
|
-
...route2,
|
|
350
|
-
url: "*"
|
|
351
|
-
});
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
354
|
-
let root = "";
|
|
355
|
-
if (!this.alepha.isServerless()) {
|
|
356
|
-
const maybe = [
|
|
357
|
-
node_path.join(process.cwd(), this.env.REACT_SERVER_DIST),
|
|
358
|
-
node_path.join(process.cwd(), "..", this.env.REACT_SERVER_DIST)
|
|
359
|
-
];
|
|
360
|
-
for (const it of maybe) {
|
|
361
|
-
if (node_fs.existsSync(it)) {
|
|
362
|
-
root = it;
|
|
363
|
-
break;
|
|
99
|
+
const pages = this.alepha.getDescriptorValues(useActive.$page);
|
|
100
|
+
if (pages.length === 0) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
for (const { key, instance, value } of pages) {
|
|
104
|
+
const name = value.options.name ?? key;
|
|
105
|
+
if (this.alepha.isTest()) {
|
|
106
|
+
instance[key].render = this.createRenderFunction(name);
|
|
364
107
|
}
|
|
365
108
|
}
|
|
366
|
-
if (
|
|
367
|
-
this.
|
|
109
|
+
if (this.alepha.isServerless() === "vite") {
|
|
110
|
+
await this.configureVite();
|
|
368
111
|
return;
|
|
369
112
|
}
|
|
370
|
-
|
|
113
|
+
let root = "";
|
|
114
|
+
if (!this.alepha.isServerless()) {
|
|
115
|
+
root = this.getPublicDirectory();
|
|
116
|
+
if (!root) {
|
|
117
|
+
this.log.warn("Missing static files, SSR will be disabled");
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
await this.configureStaticServer(root);
|
|
121
|
+
}
|
|
122
|
+
const template = this.alepha.state("ReactServerProvider.template") ?? await promises.readFile(node_path.join(root, "index.html"), "utf-8");
|
|
123
|
+
await this.registerPages(async () => template);
|
|
124
|
+
this.alepha.state("ReactServerProvider.ssr", true);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
async registerPages(templateLoader) {
|
|
128
|
+
for (const page of this.pageDescriptorProvider.getPages()) {
|
|
129
|
+
this.log.debug(`+ ${page.match} -> ${page.name}`);
|
|
130
|
+
await this.serverRouterProvider.route({
|
|
131
|
+
method: "GET",
|
|
132
|
+
path: page.match,
|
|
133
|
+
handler: this.createHandler(page, templateLoader)
|
|
134
|
+
});
|
|
371
135
|
}
|
|
372
|
-
const template = this.checkTemplate(
|
|
373
|
-
this.alepha.state("ReactServerProvider.template") ?? await promises.readFile(node_path.join(root, "index.html"), "utf-8")
|
|
374
|
-
);
|
|
375
|
-
const route = this.createHandler(async () => template);
|
|
376
|
-
await this.server.route(route);
|
|
377
|
-
await this.server.route({
|
|
378
|
-
...route,
|
|
379
|
-
url: "*"
|
|
380
|
-
});
|
|
381
136
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
if (!template.includes('<div id="root"></div>')) {
|
|
391
|
-
throw new Error(
|
|
392
|
-
`Missing React SSR outlet in index.html, please add ${this.env.REACT_SSR_OUTLET} to the index.html file`
|
|
393
|
-
);
|
|
137
|
+
getPublicDirectory() {
|
|
138
|
+
const maybe = [
|
|
139
|
+
node_path.join(process.cwd(), this.env.REACT_SERVER_DIST),
|
|
140
|
+
node_path.join(process.cwd(), "..", this.env.REACT_SERVER_DIST)
|
|
141
|
+
];
|
|
142
|
+
for (const it of maybe) {
|
|
143
|
+
if (node_fs.existsSync(it)) {
|
|
144
|
+
return it;
|
|
394
145
|
}
|
|
395
|
-
return template.replace(
|
|
396
|
-
`<div id="root"></div>`,
|
|
397
|
-
`<div id="root">${this.env.REACT_SSR_OUTLET}</div>`
|
|
398
|
-
);
|
|
399
146
|
}
|
|
400
|
-
return
|
|
147
|
+
return "";
|
|
401
148
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
* @param root
|
|
405
|
-
* @protected
|
|
406
|
-
*/
|
|
407
|
-
createStaticHandler(root) {
|
|
408
|
-
return {
|
|
149
|
+
async configureStaticServer(root) {
|
|
150
|
+
await this.serverStaticProvider.serve({
|
|
409
151
|
root,
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
cacheControl: true,
|
|
413
|
-
immutable: true,
|
|
414
|
-
preCompressed: true,
|
|
415
|
-
maxAge: "30d",
|
|
416
|
-
index: false
|
|
417
|
-
};
|
|
152
|
+
path: this.env.REACT_SERVER_PREFIX
|
|
153
|
+
});
|
|
418
154
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
}
|
|
155
|
+
async configureVite() {
|
|
156
|
+
const url = `http://${process.env.SERVER_HOST}:${process.env.SERVER_PORT}`;
|
|
157
|
+
this.log.info("SSR (vite) OK");
|
|
158
|
+
this.alepha.state("ReactServerProvider.ssr", true);
|
|
159
|
+
const templateUrl = `${url}/index.html`;
|
|
160
|
+
await this.registerPages(
|
|
161
|
+
() => fetch(templateUrl).then((it) => it.text()).catch(() => void 0)
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
createRenderFunction(name) {
|
|
165
|
+
return async (options = {}) => {
|
|
166
|
+
const page = this.pageDescriptorProvider.page(name);
|
|
167
|
+
const state = await this.pageDescriptorProvider.createLayers(page, {
|
|
168
|
+
url: new URL("http://localhost"),
|
|
169
|
+
params: options.params ?? {},
|
|
170
|
+
query: options.query ?? {},
|
|
171
|
+
head: {}
|
|
172
|
+
});
|
|
173
|
+
return server$1.renderToString(this.pageDescriptorProvider.root(state));
|
|
439
174
|
};
|
|
440
175
|
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
);
|
|
454
|
-
return server$1.renderToString(
|
|
455
|
-
this.router.root({
|
|
456
|
-
layers,
|
|
457
|
-
pathname: "",
|
|
458
|
-
search: "",
|
|
459
|
-
context: {}
|
|
460
|
-
})
|
|
461
|
-
);
|
|
176
|
+
createHandler(page, templateLoader) {
|
|
177
|
+
return async (serverRequest) => {
|
|
178
|
+
const { url, reply, query, params } = serverRequest;
|
|
179
|
+
const template = await templateLoader();
|
|
180
|
+
if (!template) {
|
|
181
|
+
throw new Error("Template not found");
|
|
182
|
+
}
|
|
183
|
+
const request = {
|
|
184
|
+
url,
|
|
185
|
+
params,
|
|
186
|
+
query,
|
|
187
|
+
head: {}
|
|
462
188
|
};
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
* @param url
|
|
468
|
-
* @protected
|
|
469
|
-
*/
|
|
470
|
-
notFoundHandler(url) {
|
|
471
|
-
if (url.pathname.match(/\.\w+$/)) {
|
|
472
|
-
return new Response("Not found", { status: 404 });
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
/**
|
|
476
|
-
*
|
|
477
|
-
* @param url
|
|
478
|
-
* @param template
|
|
479
|
-
* @param args
|
|
480
|
-
*/
|
|
481
|
-
async ssr(url, template = this.env.REACT_SSR_OUTLET, args = {}) {
|
|
482
|
-
const { element, layers, redirect, context } = await this.router.render(
|
|
483
|
-
url.pathname + url.search,
|
|
484
|
-
{
|
|
485
|
-
args
|
|
189
|
+
if (this.alepha.has(server.ServerLinksProvider)) {
|
|
190
|
+
const srv = this.alepha.get(server.ServerLinksProvider);
|
|
191
|
+
request.links = await srv.links();
|
|
192
|
+
this.alepha.als.set("links", request.links);
|
|
486
193
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
194
|
+
await this.alepha.run(
|
|
195
|
+
"react:server:render",
|
|
196
|
+
{
|
|
197
|
+
request: serverRequest,
|
|
198
|
+
pageRequest: request
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
log: false
|
|
493
202
|
}
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
203
|
+
);
|
|
204
|
+
const state = await this.pageDescriptorProvider.createLayers(
|
|
205
|
+
page,
|
|
206
|
+
request
|
|
207
|
+
);
|
|
208
|
+
if (state.redirect) {
|
|
209
|
+
return reply.redirect(state.redirect);
|
|
210
|
+
}
|
|
211
|
+
const element = this.pageDescriptorProvider.root(state, request);
|
|
212
|
+
const app = server$1.renderToString(element);
|
|
213
|
+
const script = `<script>window.__ssr=${JSON.stringify({
|
|
214
|
+
links: request.links,
|
|
215
|
+
layers: state.layers.map((it) => ({
|
|
216
|
+
...it,
|
|
217
|
+
error: it.error ? {
|
|
218
|
+
...it.error,
|
|
219
|
+
name: it.error.name,
|
|
220
|
+
message: it.error.message,
|
|
221
|
+
stack: it.error.stack
|
|
222
|
+
// TODO: Hide stack in production ?
|
|
223
|
+
} : void 0,
|
|
224
|
+
index: void 0,
|
|
225
|
+
path: void 0,
|
|
226
|
+
element: void 0
|
|
227
|
+
}))
|
|
228
|
+
})}<\/script>`;
|
|
229
|
+
const response = {
|
|
230
|
+
html: template
|
|
231
|
+
};
|
|
232
|
+
reply.status = 200;
|
|
233
|
+
reply.headers["content-type"] = "text/html";
|
|
234
|
+
reply.headers["cache-control"] = "no-store, no-cache, must-revalidate, proxy-revalidate";
|
|
235
|
+
reply.headers.pragma = "no-cache";
|
|
236
|
+
reply.headers.expires = "0";
|
|
237
|
+
this.fillTemplate(response, app, script);
|
|
238
|
+
if (state.head) {
|
|
239
|
+
response.html = this.headProvider.renderHead(response.html, state.head);
|
|
240
|
+
}
|
|
241
|
+
return response.html;
|
|
242
|
+
};
|
|
516
243
|
}
|
|
517
|
-
|
|
518
|
-
if (
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
`<
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
)
|
|
244
|
+
fillTemplate(response, app, script) {
|
|
245
|
+
if (this.ROOT_DIV_REGEX.test(response.html)) {
|
|
246
|
+
response.html = response.html.replace(
|
|
247
|
+
this.ROOT_DIV_REGEX,
|
|
248
|
+
(_match, beforeId, afterId) => {
|
|
249
|
+
return `<div${beforeId} id="${this.env.REACT_ROOT_ID}"${afterId}>${app}</div>`;
|
|
250
|
+
}
|
|
251
|
+
);
|
|
252
|
+
} else {
|
|
253
|
+
const bodyOpenTag = /<body([^>]*)>/i;
|
|
254
|
+
if (bodyOpenTag.test(response.html)) {
|
|
255
|
+
response.html = response.html.replace(bodyOpenTag, (match) => {
|
|
256
|
+
return `${match}
|
|
257
|
+
<div id="${this.env.REACT_ROOT_ID}">${app}</div>`;
|
|
258
|
+
});
|
|
529
259
|
}
|
|
530
260
|
}
|
|
531
|
-
|
|
261
|
+
const bodyCloseTagRegex = /<\/body>/i;
|
|
262
|
+
if (bodyCloseTagRegex.test(response.html)) {
|
|
263
|
+
response.html = response.html.replace(
|
|
264
|
+
bodyCloseTagRegex,
|
|
265
|
+
`${script}
|
|
266
|
+
</body>`
|
|
267
|
+
);
|
|
268
|
+
}
|
|
532
269
|
}
|
|
533
270
|
}
|
|
534
271
|
|
|
535
|
-
const envSchema = core.t.object({
|
|
536
|
-
REACT_AUTH_ENABLED: core.t.boolean({ default: false })
|
|
537
|
-
});
|
|
538
272
|
class ReactModule {
|
|
539
|
-
env = core.$inject(envSchema);
|
|
540
273
|
alepha = core.$inject(core.Alepha);
|
|
541
274
|
constructor() {
|
|
542
|
-
this.alepha.with(server.ServerModule).with(server.ServerLinksProvider).with(
|
|
543
|
-
if (this.env.REACT_AUTH_ENABLED) {
|
|
544
|
-
this.alepha.with(ReactAuthProvider);
|
|
545
|
-
this.alepha.with(useAuth.Auth);
|
|
546
|
-
}
|
|
275
|
+
this.alepha.with(server.ServerModule).with(server.ServerLinksProvider).with(useActive.PageDescriptorProvider).with(ReactServerProvider);
|
|
547
276
|
}
|
|
548
277
|
}
|
|
549
|
-
core.
|
|
550
|
-
core.autoInject(useAuth.$auth, ReactAuthProvider, useAuth.Auth);
|
|
278
|
+
core.__bind(useActive.$page, ReactModule);
|
|
551
279
|
|
|
552
|
-
exports.$
|
|
553
|
-
exports
|
|
554
|
-
exports.
|
|
555
|
-
exports.
|
|
556
|
-
exports.
|
|
557
|
-
exports.
|
|
558
|
-
exports.
|
|
559
|
-
exports.
|
|
560
|
-
exports.
|
|
561
|
-
exports.
|
|
562
|
-
exports.
|
|
563
|
-
exports.
|
|
564
|
-
exports.
|
|
565
|
-
exports.
|
|
566
|
-
exports.
|
|
567
|
-
exports.
|
|
568
|
-
exports.
|
|
569
|
-
exports.useQueryParams = useAuth.useQueryParams;
|
|
570
|
-
exports.useRouter = useAuth.useRouter;
|
|
571
|
-
exports.useRouterEvents = useAuth.useRouterEvents;
|
|
572
|
-
exports.useRouterState = useAuth.useRouterState;
|
|
573
|
-
exports.ReactAuthProvider = ReactAuthProvider;
|
|
280
|
+
exports.$page = useActive.$page;
|
|
281
|
+
exports.Link = useActive.Link;
|
|
282
|
+
exports.NestedView = useActive.NestedView;
|
|
283
|
+
exports.PageDescriptorProvider = useActive.PageDescriptorProvider;
|
|
284
|
+
exports.ReactBrowserProvider = useActive.ReactBrowserProvider;
|
|
285
|
+
exports.RedirectionError = useActive.RedirectionError;
|
|
286
|
+
exports.RouterContext = useActive.RouterContext;
|
|
287
|
+
exports.RouterHookApi = useActive.RouterHookApi;
|
|
288
|
+
exports.RouterLayerContext = useActive.RouterLayerContext;
|
|
289
|
+
exports.isPageRoute = useActive.isPageRoute;
|
|
290
|
+
exports.useActive = useActive.useActive;
|
|
291
|
+
exports.useClient = useActive.useClient;
|
|
292
|
+
exports.useInject = useActive.useInject;
|
|
293
|
+
exports.useQueryParams = useActive.useQueryParams;
|
|
294
|
+
exports.useRouter = useActive.useRouter;
|
|
295
|
+
exports.useRouterEvents = useActive.useRouterEvents;
|
|
296
|
+
exports.useRouterState = useActive.useRouterState;
|
|
574
297
|
exports.ReactModule = ReactModule;
|
|
575
298
|
exports.ReactServerProvider = ReactServerProvider;
|
|
576
|
-
exports.envSchema = envSchema
|
|
299
|
+
exports.envSchema = envSchema;
|