@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/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 useAuth = require('./useAuth-DOVx2kqa.cjs');
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('path-to-regexp');
14
+ require('@alepha/router');
15
15
 
16
- class ReactAuthProvider {
17
- log = core.$logger();
18
- alepha = core.$inject(core.Alepha);
19
- fastifyCookieProvider = core.$inject(server.FastifyCookieProvider);
20
- authProviders = [];
21
- authorizationCode = server.$cookie({
22
- name: "authorizationCode",
23
- ttl: { minutes: 15 },
24
- httpOnly: true,
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
- * @param accessToken
220
- * @protected
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
- logout = server.$route({
248
- security: false,
249
- internal: true,
250
- url: "/_oauth/logout",
251
- group: "auth",
252
- method: "GET",
253
- schema: {
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
- * @param name
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
- const authProvider = this.authProviders.find(
288
- (provider) => provider.name === name
52
+ result = result.replace(
53
+ /<head([^>]*)>(.*?)<\/head>/is,
54
+ (_, existingAttrs, existingHead) => `<head${existingAttrs}>${existingHead}${headContent}</head>`
289
55
  );
290
- if (!authProvider) {
291
- throw new server.BadRequestError(`Client ${name} not found`);
292
- }
293
- return authProvider;
56
+ return result.trim();
294
57
  }
295
- /**
296
- *
297
- * @param file
298
- * @protected
299
- */
300
- isViteFile(file) {
301
- const [pathname] = file.split("?");
302
- if (pathname.startsWith("/docs")) {
303
- return false;
304
- }
305
- if (pathname.match(/\.\w{2,5}$/)) {
306
- return true;
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 false;
71
+ return attrs;
72
+ }
73
+ escapeHtml(str) {
74
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
312
75
  }
313
76
  }
314
77
 
315
- const envSchema$1 = core.t.object({
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
- REACT_SSR_OUTLET: core.t.string({ default: "<!--ssr-outlet-->" })
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
- router = core.$inject(useAuth.Router);
325
- server = core.$inject(server.ServerProvider);
326
- env = core.$inject(envSchema$1);
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
- await this.configureRoutes();
331
- }
332
- });
333
- async configureRoutes() {
334
- if (this.alepha.isTest()) {
335
- this.processDescriptors();
336
- }
337
- if (this.router.empty()) {
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 (!root) {
367
- this.log.warn("Missing static files, SSR will be disabled");
109
+ if (this.alepha.isServerless() === "vite") {
110
+ await this.configureVite();
368
111
  return;
369
112
  }
370
- await this.server.serve(this.createStaticHandler(root));
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
- * Check if the template contains the outlet.
384
- *
385
- * @param template
386
- * @protected
387
- */
388
- checkTemplate(template) {
389
- if (!template.includes(this.env.REACT_SSR_OUTLET)) {
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 template;
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
- prefix: this.env.REACT_SERVER_PREFIX,
411
- logLevel: "warn",
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
- * @param templateLoader
422
- * @protected
423
- */
424
- createHandler(templateLoader) {
425
- return {
426
- method: "GET",
427
- url: "/",
428
- handler: async (ctx) => {
429
- const template = await templateLoader();
430
- if (!template) {
431
- return new Response("Not found", { status: 404 });
432
- }
433
- const response = this.notFoundHandler(ctx.url);
434
- if (response) {
435
- return response;
436
- }
437
- return await this.ssr(ctx.url, template, ctx);
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
- processDescriptors() {
442
- const pages = this.alepha.getDescriptorValues(useAuth.$page);
443
- for (const { key, instance, value } of pages) {
444
- instance[key].render = async (options = {}) => {
445
- const name = value.options.name ?? key;
446
- const page = this.router.page(name);
447
- const layers = await this.router.createLayers(
448
- "",
449
- page,
450
- options.params ?? {},
451
- options.query ?? {},
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
- if (redirect) {
489
- return new Response("", {
490
- status: 302,
491
- headers: {
492
- Location: redirect
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
- const appHtml = server$1.renderToString(element);
497
- const script = `<script>window.__ssr=${JSON.stringify({
498
- layers: layers.map((it) => ({
499
- ...it,
500
- index: void 0,
501
- path: void 0,
502
- element: void 0
503
- }))
504
- })}<\/script>`;
505
- const index = template.indexOf("</body>");
506
- if (index !== -1) {
507
- template = template.slice(0, index) + script + template.slice(index);
508
- }
509
- if (context.helmet) {
510
- template = this.renderHelmetContext(template, context.helmet);
511
- }
512
- template = template.replace(this.env.REACT_SSR_OUTLET, appHtml);
513
- return new Response(template, {
514
- headers: { "Content-Type": "text/html" }
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
- renderHelmetContext(template, helmetContext) {
518
- if (helmetContext.title) {
519
- if (template.includes("<title>")) {
520
- template = template.replace(
521
- /<title>.*<\/title>/,
522
- `<title>${helmetContext.title}</title>`
523
- );
524
- } else {
525
- template = template.replace(
526
- "</head>",
527
- `<title>${helmetContext.title}</title></head>`
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
- return template;
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(useAuth.PageDescriptorProvider).with(ReactServerProvider);
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.autoInject(useAuth.$page, ReactModule);
550
- core.autoInject(useAuth.$auth, ReactAuthProvider, useAuth.Auth);
278
+ core.__bind(useActive.$page, ReactModule);
551
279
 
552
- exports.$auth = useAuth.$auth;
553
- exports.$page = useAuth.$page;
554
- exports.Auth = useAuth.Auth;
555
- exports.Link = useAuth.Link;
556
- exports.NestedView = useAuth.NestedView;
557
- exports.PageDescriptorProvider = useAuth.PageDescriptorProvider;
558
- exports.ReactBrowserProvider = useAuth.ReactBrowserProvider;
559
- exports.RedirectionError = useAuth.RedirectionError;
560
- exports.Router = useAuth.Router;
561
- exports.RouterContext = useAuth.RouterContext;
562
- exports.RouterHookApi = useAuth.RouterHookApi;
563
- exports.RouterLayerContext = useAuth.RouterLayerContext;
564
- exports.pageDescriptorKey = useAuth.pageDescriptorKey;
565
- exports.useActive = useAuth.useActive;
566
- exports.useAuth = useAuth.useAuth;
567
- exports.useClient = useAuth.useClient;
568
- exports.useInject = useAuth.useInject;
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$1;
299
+ exports.envSchema = envSchema;