@alepha/react 0.6.1 → 0.6.2
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 +1 -1
- package/dist/index.browser.js +2 -2
- package/dist/index.cjs +118 -86
- package/dist/index.d.ts +70 -44
- package/dist/index.js +120 -88
- package/dist/{useAuth-DOVx2kqa.cjs → useAuth-B9ypF48n.cjs} +112 -44
- package/dist/{useAuth-i7wbKVrt.js → useAuth-Ps01oe8e.js} +113 -45
- package/package.json +13 -12
package/dist/index.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { $logger, $inject, Alepha, t, $hook, autoInject } from '@alepha/core';
|
|
2
|
-
import {
|
|
3
|
-
import { $ as $auth, R as Router, a as $page, A as Auth, P as PageDescriptorProvider } from './useAuth-
|
|
4
|
-
export { L as Link, N as NestedView, l as ReactBrowserProvider, m as RedirectionError, b as RouterContext, d as RouterHookApi, c as RouterLayerContext, p as pageDescriptorKey, j as useActive, k as useAuth, e as useClient, u as useInject, f as useQueryParams, g as useRouter, h as useRouterEvents, i as useRouterState } from './useAuth-
|
|
2
|
+
import { ServerCookieProvider, $cookie, $route, BadRequestError, ServerProvider, ServerLinksProvider, ServerModule } from '@alepha/server';
|
|
3
|
+
import { $ as $auth, R as Router, a as $page, A as Auth, P as PageDescriptorProvider } from './useAuth-Ps01oe8e.js';
|
|
4
|
+
export { L as Link, N as NestedView, l as ReactBrowserProvider, m as RedirectionError, b as RouterContext, d as RouterHookApi, c as RouterLayerContext, p as pageDescriptorKey, j as useActive, k as useAuth, e as useClient, u as useInject, f as useQueryParams, g as useRouter, h as useRouterEvents, i as useRouterState } from './useAuth-Ps01oe8e.js';
|
|
5
5
|
import { discovery, allowInsecureRequests, refreshTokenGrant, randomPKCECodeVerifier, calculatePKCECodeChallenge, buildAuthorizationUrl, authorizationCodeGrant, buildEndSessionUrl } from 'openid-client';
|
|
6
6
|
import { existsSync } from 'node:fs';
|
|
7
7
|
import { readFile } from 'node:fs/promises';
|
|
8
8
|
import { join } from 'node:path';
|
|
9
|
+
import { load } from 'cheerio';
|
|
9
10
|
import { renderToString } from 'react-dom/server';
|
|
10
11
|
import 'react/jsx-runtime';
|
|
11
12
|
import 'react';
|
|
@@ -15,7 +16,7 @@ import 'path-to-regexp';
|
|
|
15
16
|
class ReactAuthProvider {
|
|
16
17
|
log = $logger();
|
|
17
18
|
alepha = $inject(Alepha);
|
|
18
|
-
|
|
19
|
+
serverCookieProvider = $inject(ServerCookieProvider);
|
|
19
20
|
authProviders = [];
|
|
20
21
|
authorizationCode = $cookie({
|
|
21
22
|
name: "authorizationCode",
|
|
@@ -53,23 +54,30 @@ class ReactAuthProvider {
|
|
|
53
54
|
name: "configure",
|
|
54
55
|
handler: async () => {
|
|
55
56
|
const auths = this.alepha.getDescriptorValues($auth);
|
|
56
|
-
for (const
|
|
57
|
-
const options =
|
|
57
|
+
for (const { value, key, instance } of auths) {
|
|
58
|
+
const options = value.options;
|
|
58
59
|
if (options.oidc) {
|
|
60
|
+
this.log.debug(
|
|
61
|
+
`Discover OIDC auth provider -> ${options.oidc.issuer}`
|
|
62
|
+
);
|
|
63
|
+
const client = await 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: [allowInsecureRequests]
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
instance[key].jwks = () => {
|
|
75
|
+
return client.serverMetadata().jwks_uri;
|
|
76
|
+
};
|
|
59
77
|
this.authProviders.push({
|
|
60
|
-
name: options.name ??
|
|
78
|
+
name: options.name ?? key,
|
|
61
79
|
redirectUri: options.oidc.redirectUri ?? "/api/_oauth/callback",
|
|
62
|
-
client
|
|
63
|
-
new URL(options.oidc.issuer),
|
|
64
|
-
options.oidc.clientId,
|
|
65
|
-
{
|
|
66
|
-
client_secret: options.oidc.clientSecret
|
|
67
|
-
},
|
|
68
|
-
void 0,
|
|
69
|
-
{
|
|
70
|
-
execute: [allowInsecureRequests]
|
|
71
|
-
}
|
|
72
|
-
)
|
|
80
|
+
client
|
|
73
81
|
});
|
|
74
82
|
}
|
|
75
83
|
}
|
|
@@ -78,21 +86,19 @@ class ReactAuthProvider {
|
|
|
78
86
|
/**
|
|
79
87
|
* Configure Fastify to forward Session Access Token to Header Authorization.
|
|
80
88
|
*/
|
|
81
|
-
|
|
82
|
-
name: "
|
|
83
|
-
after: this.
|
|
84
|
-
handler: async (
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
req.headers.authorization = `Bearer ${tokens.access_token}`;
|
|
90
|
-
}
|
|
91
|
-
if (this.user.get(req.cookies) && !this.tokens.get(req.cookies)) {
|
|
92
|
-
this.user.del(req.cookies);
|
|
93
|
-
}
|
|
89
|
+
onRequest = $hook({
|
|
90
|
+
name: "server:onRequest",
|
|
91
|
+
after: this.serverCookieProvider,
|
|
92
|
+
handler: async ({ request }) => {
|
|
93
|
+
if (request.cookies && !this.isViteFile(request.url.pathname) && !!this.authProviders.length) {
|
|
94
|
+
const tokens = await this.refresh(request.cookies);
|
|
95
|
+
if (tokens) {
|
|
96
|
+
request.headers.rep("authorization", `Bearer ${tokens.access_token}`);
|
|
94
97
|
}
|
|
95
|
-
|
|
98
|
+
if (this.user.get(request.cookies) && !this.tokens.get(request.cookies)) {
|
|
99
|
+
this.user.del(request.cookies);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
96
102
|
}
|
|
97
103
|
});
|
|
98
104
|
/**
|
|
@@ -121,7 +127,9 @@ class ReactAuthProvider {
|
|
|
121
127
|
});
|
|
122
128
|
return newTokens;
|
|
123
129
|
} catch (e) {
|
|
124
|
-
|
|
130
|
+
if (e instanceof Error) {
|
|
131
|
+
this.log.warn("Failed to refresh token", e.message);
|
|
132
|
+
}
|
|
125
133
|
}
|
|
126
134
|
}
|
|
127
135
|
this.tokens.del(cookies);
|
|
@@ -315,13 +323,13 @@ const envSchema$1 = t.object({
|
|
|
315
323
|
REACT_SERVER_DIST: t.string({ default: "client" }),
|
|
316
324
|
REACT_SERVER_PREFIX: t.string({ default: "" }),
|
|
317
325
|
REACT_SSR_ENABLED: t.boolean({ default: false }),
|
|
318
|
-
|
|
326
|
+
REACT_ROOT_ID: t.string({ default: "root" })
|
|
319
327
|
});
|
|
320
328
|
class ReactServerProvider {
|
|
321
329
|
log = $logger();
|
|
322
330
|
alepha = $inject(Alepha);
|
|
323
331
|
router = $inject(Router);
|
|
324
|
-
|
|
332
|
+
serverProvider = $inject(ServerProvider);
|
|
325
333
|
env = $inject(envSchema$1);
|
|
326
334
|
configure = $hook({
|
|
327
335
|
name: "configure",
|
|
@@ -329,7 +337,9 @@ class ReactServerProvider {
|
|
|
329
337
|
await this.configureRoutes();
|
|
330
338
|
}
|
|
331
339
|
});
|
|
340
|
+
id = Math.random().toString(36).substring(2, 7);
|
|
332
341
|
async configureRoutes() {
|
|
342
|
+
this.alepha.state("ReactServerProvider.ssr", false);
|
|
333
343
|
if (this.alepha.isTest()) {
|
|
334
344
|
this.processDescriptors();
|
|
335
345
|
}
|
|
@@ -337,14 +347,15 @@ class ReactServerProvider {
|
|
|
337
347
|
return;
|
|
338
348
|
}
|
|
339
349
|
if (process.env.VITE_ALEPHA_DEV === "true") {
|
|
350
|
+
const url = `http://${process.env.SERVER_HOST}:${process.env.SERVER_PORT}`;
|
|
340
351
|
this.log.info("SSR (vite) OK");
|
|
341
|
-
|
|
342
|
-
|
|
352
|
+
this.alepha.state("ReactServerProvider.ssr", true);
|
|
353
|
+
const templateUrl = `${url}/index.html`;
|
|
343
354
|
const route2 = this.createHandler(
|
|
344
|
-
() => fetch(templateUrl).then((it) => it.text()).catch(() => void 0)
|
|
355
|
+
() => fetch(templateUrl).then((it) => it.text()).catch(() => void 0)
|
|
345
356
|
);
|
|
346
|
-
await this.
|
|
347
|
-
await this.
|
|
357
|
+
await this.serverProvider.route(route2);
|
|
358
|
+
await this.serverProvider.route({
|
|
348
359
|
...route2,
|
|
349
360
|
url: "*"
|
|
350
361
|
});
|
|
@@ -366,37 +377,16 @@ class ReactServerProvider {
|
|
|
366
377
|
this.log.warn("Missing static files, SSR will be disabled");
|
|
367
378
|
return;
|
|
368
379
|
}
|
|
369
|
-
await this.
|
|
380
|
+
await this.serverProvider.serve(this.createStaticHandler(root));
|
|
370
381
|
}
|
|
371
|
-
const template = this.
|
|
372
|
-
this.alepha.state("ReactServerProvider.template") ?? await readFile(join(root, "index.html"), "utf-8")
|
|
373
|
-
);
|
|
382
|
+
const template = this.alepha.state("ReactServerProvider.template") ?? await readFile(join(root, "index.html"), "utf-8");
|
|
374
383
|
const route = this.createHandler(async () => template);
|
|
375
|
-
await this.
|
|
376
|
-
await this.
|
|
384
|
+
await this.serverProvider.route(route);
|
|
385
|
+
await this.serverProvider.route({
|
|
377
386
|
...route,
|
|
378
387
|
url: "*"
|
|
379
388
|
});
|
|
380
|
-
|
|
381
|
-
/**
|
|
382
|
-
* Check if the template contains the outlet.
|
|
383
|
-
*
|
|
384
|
-
* @param template
|
|
385
|
-
* @protected
|
|
386
|
-
*/
|
|
387
|
-
checkTemplate(template) {
|
|
388
|
-
if (!template.includes(this.env.REACT_SSR_OUTLET)) {
|
|
389
|
-
if (!template.includes('<div id="root"></div>')) {
|
|
390
|
-
throw new Error(
|
|
391
|
-
`Missing React SSR outlet in index.html, please add ${this.env.REACT_SSR_OUTLET} to the index.html file`
|
|
392
|
-
);
|
|
393
|
-
}
|
|
394
|
-
return template.replace(
|
|
395
|
-
`<div id="root"></div>`,
|
|
396
|
-
`<div id="root">${this.env.REACT_SSR_OUTLET}</div>`
|
|
397
|
-
);
|
|
398
|
-
}
|
|
399
|
-
return template;
|
|
389
|
+
this.alepha.state("ReactServerProvider.ssr", true);
|
|
400
390
|
}
|
|
401
391
|
/**
|
|
402
392
|
*
|
|
@@ -437,6 +427,10 @@ class ReactServerProvider {
|
|
|
437
427
|
}
|
|
438
428
|
};
|
|
439
429
|
}
|
|
430
|
+
/**
|
|
431
|
+
*
|
|
432
|
+
* @protected
|
|
433
|
+
*/
|
|
440
434
|
processDescriptors() {
|
|
441
435
|
const pages = this.alepha.getDescriptorValues($page);
|
|
442
436
|
for (const { key, instance, value } of pages) {
|
|
@@ -477,7 +471,20 @@ class ReactServerProvider {
|
|
|
477
471
|
* @param template
|
|
478
472
|
* @param args
|
|
479
473
|
*/
|
|
480
|
-
async ssr(url, template
|
|
474
|
+
async ssr(url, template, args = {}) {
|
|
475
|
+
const hasAuth = this.alepha.has(ReactAuthProvider);
|
|
476
|
+
if (!args.user && args.cookies && hasAuth) {
|
|
477
|
+
const auth = this.alepha.get(ReactAuthProvider);
|
|
478
|
+
args.user = auth.user.get(args.cookies);
|
|
479
|
+
if (args.user) {
|
|
480
|
+
args.user.roles = [];
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
if (this.alepha.has(ServerLinksProvider) && hasAuth) {
|
|
484
|
+
const srv = this.alepha.get(ServerLinksProvider);
|
|
485
|
+
args.links = await srv.links();
|
|
486
|
+
this.alepha.als.set("links", args.links);
|
|
487
|
+
}
|
|
481
488
|
const { element, layers, redirect, context } = await this.router.render(
|
|
482
489
|
url.pathname + url.search,
|
|
483
490
|
{
|
|
@@ -492,42 +499,67 @@ class ReactServerProvider {
|
|
|
492
499
|
}
|
|
493
500
|
});
|
|
494
501
|
}
|
|
495
|
-
const
|
|
502
|
+
const html = renderToString(element);
|
|
503
|
+
const $ = load(template);
|
|
496
504
|
const script = `<script>window.__ssr=${JSON.stringify({
|
|
505
|
+
links: args.links,
|
|
497
506
|
layers: layers.map((it) => ({
|
|
498
507
|
...it,
|
|
508
|
+
error: it.error ? {
|
|
509
|
+
...it.error,
|
|
510
|
+
name: it.error.name,
|
|
511
|
+
message: it.error.message,
|
|
512
|
+
stack: it.error.stack
|
|
513
|
+
// TODO: Hide stack in production ?
|
|
514
|
+
} : void 0,
|
|
499
515
|
index: void 0,
|
|
500
516
|
path: void 0,
|
|
501
517
|
element: void 0
|
|
502
518
|
}))
|
|
503
519
|
})}<\/script>`;
|
|
504
|
-
const
|
|
505
|
-
|
|
506
|
-
|
|
520
|
+
const body = $("body");
|
|
521
|
+
const root = body.find(`#${this.env.REACT_ROOT_ID}`);
|
|
522
|
+
if (root.length) {
|
|
523
|
+
root.html(html);
|
|
524
|
+
} else {
|
|
525
|
+
body.prepend(`<div id="${this.env.REACT_ROOT_ID}">${html}</div>`);
|
|
507
526
|
}
|
|
508
|
-
|
|
509
|
-
|
|
527
|
+
body.append(script);
|
|
528
|
+
if (context.head) {
|
|
529
|
+
this.renderHeadContext($, context.head);
|
|
510
530
|
}
|
|
511
|
-
|
|
512
|
-
return new Response(template, {
|
|
531
|
+
return new Response($.html(), {
|
|
513
532
|
headers: { "Content-Type": "text/html" }
|
|
514
533
|
});
|
|
515
534
|
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
535
|
+
renderHeadContext($, headContext) {
|
|
536
|
+
const head = $("head");
|
|
537
|
+
if (head) {
|
|
538
|
+
if (headContext.title) {
|
|
539
|
+
head.find("title").remove();
|
|
540
|
+
head.append(`<title>${headContext.title}</title>`);
|
|
541
|
+
}
|
|
542
|
+
if (headContext.meta) {
|
|
543
|
+
for (const it of headContext.meta) {
|
|
544
|
+
const meta = head.find(`meta[name="${it.name}"]`);
|
|
545
|
+
if (meta.length) {
|
|
546
|
+
meta.attr("content", it.content);
|
|
547
|
+
} else {
|
|
548
|
+
head.append(`<meta name="${it.name}" content="${it.content}" />`);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
if (headContext.htmlAttributes) {
|
|
554
|
+
for (const [key, value] of Object.entries(headContext.htmlAttributes)) {
|
|
555
|
+
$("html").attr(key, value);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
if (headContext.bodyAttributes) {
|
|
559
|
+
for (const [key, value] of Object.entries(headContext.bodyAttributes)) {
|
|
560
|
+
$("body").attr(key, value);
|
|
528
561
|
}
|
|
529
562
|
}
|
|
530
|
-
return template;
|
|
531
563
|
}
|
|
532
564
|
}
|
|
533
565
|
|
|
@@ -12,7 +12,10 @@ const $auth = (options) => {
|
|
|
12
12
|
core.__descriptor(KEY);
|
|
13
13
|
return {
|
|
14
14
|
[core.KIND]: KEY,
|
|
15
|
-
options
|
|
15
|
+
options,
|
|
16
|
+
jwks: () => {
|
|
17
|
+
return options.oidc?.issuer ?? "";
|
|
18
|
+
}
|
|
16
19
|
};
|
|
17
20
|
};
|
|
18
21
|
$auth[core.KIND] = KEY;
|
|
@@ -95,10 +98,7 @@ class Router extends core.EventEmitter {
|
|
|
95
98
|
state,
|
|
96
99
|
router: this,
|
|
97
100
|
alepha: this.alepha,
|
|
98
|
-
args:
|
|
99
|
-
user: context.user,
|
|
100
|
-
cookies: context.cookies
|
|
101
|
-
}
|
|
101
|
+
args: context
|
|
102
102
|
}
|
|
103
103
|
},
|
|
104
104
|
state.layers[0]?.element
|
|
@@ -265,6 +265,7 @@ class Router extends core.EventEmitter {
|
|
|
265
265
|
});
|
|
266
266
|
if (prev === curr) {
|
|
267
267
|
it.props = previous[i].props;
|
|
268
|
+
it.error = previous[i].error;
|
|
268
269
|
context = {
|
|
269
270
|
...context,
|
|
270
271
|
...it.props
|
|
@@ -307,7 +308,7 @@ class Router extends core.EventEmitter {
|
|
|
307
308
|
for (const key of Object.keys(params2)) {
|
|
308
309
|
params2[key] = String(params2[key]);
|
|
309
310
|
}
|
|
310
|
-
if (it.route.
|
|
311
|
+
if (it.route.head && renderContext && !it.error) {
|
|
311
312
|
this.mergeRenderContext(it.route, renderContext, {
|
|
312
313
|
...props,
|
|
313
314
|
...context
|
|
@@ -318,13 +319,14 @@ class Router extends core.EventEmitter {
|
|
|
318
319
|
const path = acc.replace(/\/+/, "/");
|
|
319
320
|
if (it.error) {
|
|
320
321
|
const errorHandler = this.getErrorHandler(it.route);
|
|
321
|
-
const element = errorHandler ? errorHandler({
|
|
322
|
+
const element = await (errorHandler ? errorHandler({
|
|
322
323
|
...it.config,
|
|
323
324
|
error: it.error,
|
|
324
325
|
url
|
|
325
|
-
}) : this.renderError(it.error);
|
|
326
|
+
}) : this.renderError(it.error));
|
|
326
327
|
layers.push({
|
|
327
328
|
props,
|
|
329
|
+
error: it.error,
|
|
328
330
|
name: it.route.name,
|
|
329
331
|
part: it.route.path,
|
|
330
332
|
config: it.config,
|
|
@@ -388,15 +390,32 @@ class Router extends core.EventEmitter {
|
|
|
388
390
|
* @protected
|
|
389
391
|
*/
|
|
390
392
|
mergeRenderContext(page, ctx, props) {
|
|
391
|
-
if (page.
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
393
|
+
if (page.head) {
|
|
394
|
+
ctx.head ??= {};
|
|
395
|
+
const head = typeof page.head === "function" ? page.head(props, ctx.head) : page.head;
|
|
396
|
+
if (head.title) {
|
|
397
|
+
ctx.head ??= {};
|
|
398
|
+
if (ctx.head.titleSeparator) {
|
|
399
|
+
ctx.head.title = `${head.title}${ctx.head.titleSeparator}${ctx.head.title}`;
|
|
397
400
|
} else {
|
|
398
|
-
ctx.
|
|
401
|
+
ctx.head.title = head.title;
|
|
399
402
|
}
|
|
403
|
+
ctx.head.titleSeparator = head.titleSeparator;
|
|
404
|
+
}
|
|
405
|
+
if (head.htmlAttributes) {
|
|
406
|
+
ctx.head.htmlAttributes = {
|
|
407
|
+
...ctx.head.htmlAttributes,
|
|
408
|
+
...head.htmlAttributes
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
if (head.bodyAttributes) {
|
|
412
|
+
ctx.head.bodyAttributes = {
|
|
413
|
+
...ctx.head.bodyAttributes,
|
|
414
|
+
...head.bodyAttributes
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
if (head.meta) {
|
|
418
|
+
ctx.head.meta = [...ctx.head.meta ?? [], ...head.meta ?? []];
|
|
400
419
|
}
|
|
401
420
|
}
|
|
402
421
|
}
|
|
@@ -565,10 +584,14 @@ class PageDescriptorProvider {
|
|
|
565
584
|
}
|
|
566
585
|
}
|
|
567
586
|
|
|
587
|
+
const envSchema = core.t.object({
|
|
588
|
+
REACT_ROOT_ID: core.t.string({ default: "root" })
|
|
589
|
+
});
|
|
568
590
|
class ReactBrowserProvider {
|
|
569
591
|
log = core.$logger();
|
|
570
592
|
client = core.$inject(server.HttpClient);
|
|
571
593
|
router = core.$inject(Router);
|
|
594
|
+
env = core.$inject(envSchema);
|
|
572
595
|
root;
|
|
573
596
|
transitioning;
|
|
574
597
|
state = {
|
|
@@ -656,12 +679,49 @@ class ReactBrowserProvider {
|
|
|
656
679
|
return await this.render({ url: result.redirect });
|
|
657
680
|
}
|
|
658
681
|
this.transitioning = void 0;
|
|
659
|
-
return { url };
|
|
682
|
+
return { url, context: result.context };
|
|
660
683
|
}
|
|
661
|
-
|
|
684
|
+
/**
|
|
685
|
+
* Render the helmet context.
|
|
686
|
+
*
|
|
687
|
+
* @param ctx
|
|
688
|
+
* @protected
|
|
689
|
+
*/
|
|
690
|
+
renderHeadContext(ctx) {
|
|
662
691
|
if (ctx.title) {
|
|
663
692
|
this.document.title = ctx.title;
|
|
664
693
|
}
|
|
694
|
+
if (ctx.bodyAttributes) {
|
|
695
|
+
for (const [key, value] of Object.entries(ctx.bodyAttributes)) {
|
|
696
|
+
if (value) {
|
|
697
|
+
this.document.body.setAttribute(key, value);
|
|
698
|
+
} else {
|
|
699
|
+
this.document.body.removeAttribute(key);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
if (ctx.htmlAttributes) {
|
|
704
|
+
for (const [key, value] of Object.entries(ctx.htmlAttributes)) {
|
|
705
|
+
if (value) {
|
|
706
|
+
this.document.documentElement.setAttribute(key, value);
|
|
707
|
+
} else {
|
|
708
|
+
this.document.documentElement.removeAttribute(key);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
if (ctx.meta) {
|
|
713
|
+
for (const [key, value] of Object.entries(ctx.meta)) {
|
|
714
|
+
const meta = this.document.querySelector(`meta[name="${key}"]`);
|
|
715
|
+
if (meta) {
|
|
716
|
+
meta.setAttribute("content", value.content);
|
|
717
|
+
} else {
|
|
718
|
+
const newMeta = this.document.createElement("meta");
|
|
719
|
+
newMeta.setAttribute("name", key);
|
|
720
|
+
newMeta.setAttribute("content", value.content);
|
|
721
|
+
this.document.head.appendChild(newMeta);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
}
|
|
665
725
|
}
|
|
666
726
|
/**
|
|
667
727
|
* Get embedded layers from the server.
|
|
@@ -682,13 +742,13 @@ class ReactBrowserProvider {
|
|
|
682
742
|
* @protected
|
|
683
743
|
*/
|
|
684
744
|
getRootElement() {
|
|
685
|
-
const root = this.document.getElementById(
|
|
745
|
+
const root = this.document.getElementById(this.env.REACT_ROOT_ID);
|
|
686
746
|
if (root) {
|
|
687
747
|
return root;
|
|
688
748
|
}
|
|
689
749
|
const div = this.document.createElement("div");
|
|
690
|
-
div.id =
|
|
691
|
-
this.document.body.
|
|
750
|
+
div.id = this.env.REACT_ROOT_ID;
|
|
751
|
+
this.document.body.prepend(div);
|
|
692
752
|
return div;
|
|
693
753
|
}
|
|
694
754
|
getUserFromCookies() {
|
|
@@ -713,7 +773,13 @@ class ReactBrowserProvider {
|
|
|
713
773
|
handler: async () => {
|
|
714
774
|
const cache = this.getHydrationState();
|
|
715
775
|
const previous = cache?.layers ?? [];
|
|
716
|
-
|
|
776
|
+
if (cache?.links) {
|
|
777
|
+
this.client.links = cache.links;
|
|
778
|
+
}
|
|
779
|
+
const { context } = await this.render({ previous });
|
|
780
|
+
if (context.head) {
|
|
781
|
+
this.renderHeadContext(context.head);
|
|
782
|
+
}
|
|
717
783
|
const element = this.router.root(this.state, {
|
|
718
784
|
user: cache?.user ?? this.getUserFromCookies()
|
|
719
785
|
});
|
|
@@ -721,40 +787,30 @@ class ReactBrowserProvider {
|
|
|
721
787
|
this.root = client.hydrateRoot(this.getRootElement(), element);
|
|
722
788
|
this.log.info("Hydrated root element");
|
|
723
789
|
} else {
|
|
724
|
-
this.root
|
|
790
|
+
this.root ??= client.createRoot(this.getRootElement());
|
|
725
791
|
this.root.render(element);
|
|
726
792
|
this.log.info("Created root element");
|
|
727
793
|
}
|
|
728
794
|
window.addEventListener("popstate", () => {
|
|
729
795
|
this.render();
|
|
730
796
|
});
|
|
731
|
-
this.router.on("end", ({ context }) => {
|
|
732
|
-
if (
|
|
733
|
-
this.
|
|
797
|
+
this.router.on("end", ({ context: context2 }) => {
|
|
798
|
+
if (context2.head) {
|
|
799
|
+
this.renderHeadContext(context2.head);
|
|
734
800
|
}
|
|
735
801
|
});
|
|
736
802
|
}
|
|
737
803
|
});
|
|
738
|
-
/**
|
|
739
|
-
*
|
|
740
|
-
* @protected
|
|
741
|
-
*/
|
|
742
|
-
stop = core.$hook({
|
|
743
|
-
name: "stop",
|
|
744
|
-
handler: async () => {
|
|
745
|
-
if (this.root) {
|
|
746
|
-
this.root.unmount();
|
|
747
|
-
this.log.info("Unmounted root element");
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
});
|
|
751
804
|
}
|
|
752
805
|
|
|
753
806
|
class Auth {
|
|
754
807
|
alepha = core.$inject(core.Alepha);
|
|
755
808
|
log = core.$logger();
|
|
756
809
|
client = core.$inject(server.HttpClient);
|
|
757
|
-
|
|
810
|
+
slugs = {
|
|
811
|
+
login: "/api/_oauth/login",
|
|
812
|
+
logout: "/api/_oauth/logout"
|
|
813
|
+
};
|
|
758
814
|
start = core.$hook({
|
|
759
815
|
name: "start",
|
|
760
816
|
handler: async () => {
|
|
@@ -769,16 +825,16 @@ class Auth {
|
|
|
769
825
|
if (this.alepha.isBrowser()) {
|
|
770
826
|
const browser = this.alepha.get(ReactBrowserProvider);
|
|
771
827
|
const redirect = browser.transitioning ? window.location.origin + browser.transitioning.to : window.location.href;
|
|
772
|
-
window.location.href = `${this.
|
|
828
|
+
window.location.href = `${this.slugs.login}?redirect=${redirect}`;
|
|
773
829
|
if (browser.transitioning) {
|
|
774
830
|
throw new RedirectionError(browser.state.pathname);
|
|
775
831
|
}
|
|
776
832
|
return;
|
|
777
833
|
}
|
|
778
|
-
throw new RedirectionError(this.
|
|
834
|
+
throw new RedirectionError(this.slugs.login);
|
|
779
835
|
};
|
|
780
836
|
logout = () => {
|
|
781
|
-
window.location.href =
|
|
837
|
+
window.location.href = `${this.slugs.logout}?redirect=${encodeURIComponent(window.location.origin)}`;
|
|
782
838
|
};
|
|
783
839
|
}
|
|
784
840
|
|
|
@@ -908,8 +964,18 @@ const useRouter = () => {
|
|
|
908
964
|
|
|
909
965
|
const Link = (props) => {
|
|
910
966
|
React.useContext(RouterContext);
|
|
967
|
+
const to = typeof props.to === "string" ? props.to : props.to.options.path;
|
|
968
|
+
if (!to) {
|
|
969
|
+
return null;
|
|
970
|
+
}
|
|
971
|
+
const can = typeof props.to === "string" ? void 0 : props.to.options.can;
|
|
972
|
+
if (can && !can()) {
|
|
973
|
+
console.log("I cannot go to", to);
|
|
974
|
+
return null;
|
|
975
|
+
}
|
|
976
|
+
const name = typeof props.to === "string" ? void 0 : props.to.options.name;
|
|
911
977
|
const router = useRouter();
|
|
912
|
-
return /* @__PURE__ */ jsxRuntime.jsx("a", { ...router.createAnchorProps(
|
|
978
|
+
return /* @__PURE__ */ jsxRuntime.jsx("a", { ...router.createAnchorProps(to), ...props, children: props.children ?? name });
|
|
913
979
|
};
|
|
914
980
|
|
|
915
981
|
const useInject = (clazz) => {
|
|
@@ -917,7 +983,9 @@ const useInject = (clazz) => {
|
|
|
917
983
|
if (!ctx) {
|
|
918
984
|
throw new Error("useRouter must be used within a <RouterProvider>");
|
|
919
985
|
}
|
|
920
|
-
return ctx.alepha.get(clazz
|
|
986
|
+
return ctx.alepha.get(clazz, {
|
|
987
|
+
skipRegistration: true
|
|
988
|
+
});
|
|
921
989
|
};
|
|
922
990
|
|
|
923
991
|
const useClient = () => {
|