@alepha/react 0.6.0 → 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 +127 -98
- package/dist/index.d.ts +71 -45
- package/dist/index.js +129 -100
- 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/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
|
@@ -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 = () => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { __descriptor, KIND, NotImplementedError, EventEmitter, $logger, $inject, Alepha, $hook } from '@alepha/core';
|
|
1
|
+
import { __descriptor, KIND, NotImplementedError, EventEmitter, $logger, $inject, Alepha, $hook, t } from '@alepha/core';
|
|
2
2
|
import { jsx } from 'react/jsx-runtime';
|
|
3
3
|
import React, { createContext, useContext, useState, useEffect, createElement, useMemo } from 'react';
|
|
4
4
|
import { HttpClient } from '@alepha/server';
|
|
@@ -10,7 +10,10 @@ const $auth = (options) => {
|
|
|
10
10
|
__descriptor(KEY);
|
|
11
11
|
return {
|
|
12
12
|
[KIND]: KEY,
|
|
13
|
-
options
|
|
13
|
+
options,
|
|
14
|
+
jwks: () => {
|
|
15
|
+
return options.oidc?.issuer ?? "";
|
|
16
|
+
}
|
|
14
17
|
};
|
|
15
18
|
};
|
|
16
19
|
$auth[KIND] = KEY;
|
|
@@ -93,10 +96,7 @@ class Router extends EventEmitter {
|
|
|
93
96
|
state,
|
|
94
97
|
router: this,
|
|
95
98
|
alepha: this.alepha,
|
|
96
|
-
args:
|
|
97
|
-
user: context.user,
|
|
98
|
-
cookies: context.cookies
|
|
99
|
-
}
|
|
99
|
+
args: context
|
|
100
100
|
}
|
|
101
101
|
},
|
|
102
102
|
state.layers[0]?.element
|
|
@@ -263,6 +263,7 @@ class Router extends EventEmitter {
|
|
|
263
263
|
});
|
|
264
264
|
if (prev === curr) {
|
|
265
265
|
it.props = previous[i].props;
|
|
266
|
+
it.error = previous[i].error;
|
|
266
267
|
context = {
|
|
267
268
|
...context,
|
|
268
269
|
...it.props
|
|
@@ -305,7 +306,7 @@ class Router extends EventEmitter {
|
|
|
305
306
|
for (const key of Object.keys(params2)) {
|
|
306
307
|
params2[key] = String(params2[key]);
|
|
307
308
|
}
|
|
308
|
-
if (it.route.
|
|
309
|
+
if (it.route.head && renderContext && !it.error) {
|
|
309
310
|
this.mergeRenderContext(it.route, renderContext, {
|
|
310
311
|
...props,
|
|
311
312
|
...context
|
|
@@ -316,13 +317,14 @@ class Router extends EventEmitter {
|
|
|
316
317
|
const path = acc.replace(/\/+/, "/");
|
|
317
318
|
if (it.error) {
|
|
318
319
|
const errorHandler = this.getErrorHandler(it.route);
|
|
319
|
-
const element = errorHandler ? errorHandler({
|
|
320
|
+
const element = await (errorHandler ? errorHandler({
|
|
320
321
|
...it.config,
|
|
321
322
|
error: it.error,
|
|
322
323
|
url
|
|
323
|
-
}) : this.renderError(it.error);
|
|
324
|
+
}) : this.renderError(it.error));
|
|
324
325
|
layers.push({
|
|
325
326
|
props,
|
|
327
|
+
error: it.error,
|
|
326
328
|
name: it.route.name,
|
|
327
329
|
part: it.route.path,
|
|
328
330
|
config: it.config,
|
|
@@ -386,15 +388,32 @@ class Router extends EventEmitter {
|
|
|
386
388
|
* @protected
|
|
387
389
|
*/
|
|
388
390
|
mergeRenderContext(page, ctx, props) {
|
|
389
|
-
if (page.
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
391
|
+
if (page.head) {
|
|
392
|
+
ctx.head ??= {};
|
|
393
|
+
const head = typeof page.head === "function" ? page.head(props, ctx.head) : page.head;
|
|
394
|
+
if (head.title) {
|
|
395
|
+
ctx.head ??= {};
|
|
396
|
+
if (ctx.head.titleSeparator) {
|
|
397
|
+
ctx.head.title = `${head.title}${ctx.head.titleSeparator}${ctx.head.title}`;
|
|
395
398
|
} else {
|
|
396
|
-
ctx.
|
|
399
|
+
ctx.head.title = head.title;
|
|
397
400
|
}
|
|
401
|
+
ctx.head.titleSeparator = head.titleSeparator;
|
|
402
|
+
}
|
|
403
|
+
if (head.htmlAttributes) {
|
|
404
|
+
ctx.head.htmlAttributes = {
|
|
405
|
+
...ctx.head.htmlAttributes,
|
|
406
|
+
...head.htmlAttributes
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
if (head.bodyAttributes) {
|
|
410
|
+
ctx.head.bodyAttributes = {
|
|
411
|
+
...ctx.head.bodyAttributes,
|
|
412
|
+
...head.bodyAttributes
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
if (head.meta) {
|
|
416
|
+
ctx.head.meta = [...ctx.head.meta ?? [], ...head.meta ?? []];
|
|
398
417
|
}
|
|
399
418
|
}
|
|
400
419
|
}
|
|
@@ -563,10 +582,14 @@ class PageDescriptorProvider {
|
|
|
563
582
|
}
|
|
564
583
|
}
|
|
565
584
|
|
|
585
|
+
const envSchema = t.object({
|
|
586
|
+
REACT_ROOT_ID: t.string({ default: "root" })
|
|
587
|
+
});
|
|
566
588
|
class ReactBrowserProvider {
|
|
567
589
|
log = $logger();
|
|
568
590
|
client = $inject(HttpClient);
|
|
569
591
|
router = $inject(Router);
|
|
592
|
+
env = $inject(envSchema);
|
|
570
593
|
root;
|
|
571
594
|
transitioning;
|
|
572
595
|
state = {
|
|
@@ -654,12 +677,49 @@ class ReactBrowserProvider {
|
|
|
654
677
|
return await this.render({ url: result.redirect });
|
|
655
678
|
}
|
|
656
679
|
this.transitioning = void 0;
|
|
657
|
-
return { url };
|
|
680
|
+
return { url, context: result.context };
|
|
658
681
|
}
|
|
659
|
-
|
|
682
|
+
/**
|
|
683
|
+
* Render the helmet context.
|
|
684
|
+
*
|
|
685
|
+
* @param ctx
|
|
686
|
+
* @protected
|
|
687
|
+
*/
|
|
688
|
+
renderHeadContext(ctx) {
|
|
660
689
|
if (ctx.title) {
|
|
661
690
|
this.document.title = ctx.title;
|
|
662
691
|
}
|
|
692
|
+
if (ctx.bodyAttributes) {
|
|
693
|
+
for (const [key, value] of Object.entries(ctx.bodyAttributes)) {
|
|
694
|
+
if (value) {
|
|
695
|
+
this.document.body.setAttribute(key, value);
|
|
696
|
+
} else {
|
|
697
|
+
this.document.body.removeAttribute(key);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
if (ctx.htmlAttributes) {
|
|
702
|
+
for (const [key, value] of Object.entries(ctx.htmlAttributes)) {
|
|
703
|
+
if (value) {
|
|
704
|
+
this.document.documentElement.setAttribute(key, value);
|
|
705
|
+
} else {
|
|
706
|
+
this.document.documentElement.removeAttribute(key);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
if (ctx.meta) {
|
|
711
|
+
for (const [key, value] of Object.entries(ctx.meta)) {
|
|
712
|
+
const meta = this.document.querySelector(`meta[name="${key}"]`);
|
|
713
|
+
if (meta) {
|
|
714
|
+
meta.setAttribute("content", value.content);
|
|
715
|
+
} else {
|
|
716
|
+
const newMeta = this.document.createElement("meta");
|
|
717
|
+
newMeta.setAttribute("name", key);
|
|
718
|
+
newMeta.setAttribute("content", value.content);
|
|
719
|
+
this.document.head.appendChild(newMeta);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
}
|
|
663
723
|
}
|
|
664
724
|
/**
|
|
665
725
|
* Get embedded layers from the server.
|
|
@@ -680,13 +740,13 @@ class ReactBrowserProvider {
|
|
|
680
740
|
* @protected
|
|
681
741
|
*/
|
|
682
742
|
getRootElement() {
|
|
683
|
-
const root = this.document.getElementById(
|
|
743
|
+
const root = this.document.getElementById(this.env.REACT_ROOT_ID);
|
|
684
744
|
if (root) {
|
|
685
745
|
return root;
|
|
686
746
|
}
|
|
687
747
|
const div = this.document.createElement("div");
|
|
688
|
-
div.id =
|
|
689
|
-
this.document.body.
|
|
748
|
+
div.id = this.env.REACT_ROOT_ID;
|
|
749
|
+
this.document.body.prepend(div);
|
|
690
750
|
return div;
|
|
691
751
|
}
|
|
692
752
|
getUserFromCookies() {
|
|
@@ -711,7 +771,13 @@ class ReactBrowserProvider {
|
|
|
711
771
|
handler: async () => {
|
|
712
772
|
const cache = this.getHydrationState();
|
|
713
773
|
const previous = cache?.layers ?? [];
|
|
714
|
-
|
|
774
|
+
if (cache?.links) {
|
|
775
|
+
this.client.links = cache.links;
|
|
776
|
+
}
|
|
777
|
+
const { context } = await this.render({ previous });
|
|
778
|
+
if (context.head) {
|
|
779
|
+
this.renderHeadContext(context.head);
|
|
780
|
+
}
|
|
715
781
|
const element = this.router.root(this.state, {
|
|
716
782
|
user: cache?.user ?? this.getUserFromCookies()
|
|
717
783
|
});
|
|
@@ -719,40 +785,30 @@ class ReactBrowserProvider {
|
|
|
719
785
|
this.root = hydrateRoot(this.getRootElement(), element);
|
|
720
786
|
this.log.info("Hydrated root element");
|
|
721
787
|
} else {
|
|
722
|
-
this.root
|
|
788
|
+
this.root ??= createRoot(this.getRootElement());
|
|
723
789
|
this.root.render(element);
|
|
724
790
|
this.log.info("Created root element");
|
|
725
791
|
}
|
|
726
792
|
window.addEventListener("popstate", () => {
|
|
727
793
|
this.render();
|
|
728
794
|
});
|
|
729
|
-
this.router.on("end", ({ context }) => {
|
|
730
|
-
if (
|
|
731
|
-
this.
|
|
795
|
+
this.router.on("end", ({ context: context2 }) => {
|
|
796
|
+
if (context2.head) {
|
|
797
|
+
this.renderHeadContext(context2.head);
|
|
732
798
|
}
|
|
733
799
|
});
|
|
734
800
|
}
|
|
735
801
|
});
|
|
736
|
-
/**
|
|
737
|
-
*
|
|
738
|
-
* @protected
|
|
739
|
-
*/
|
|
740
|
-
stop = $hook({
|
|
741
|
-
name: "stop",
|
|
742
|
-
handler: async () => {
|
|
743
|
-
if (this.root) {
|
|
744
|
-
this.root.unmount();
|
|
745
|
-
this.log.info("Unmounted root element");
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
});
|
|
749
802
|
}
|
|
750
803
|
|
|
751
804
|
class Auth {
|
|
752
805
|
alepha = $inject(Alepha);
|
|
753
806
|
log = $logger();
|
|
754
807
|
client = $inject(HttpClient);
|
|
755
|
-
|
|
808
|
+
slugs = {
|
|
809
|
+
login: "/api/_oauth/login",
|
|
810
|
+
logout: "/api/_oauth/logout"
|
|
811
|
+
};
|
|
756
812
|
start = $hook({
|
|
757
813
|
name: "start",
|
|
758
814
|
handler: async () => {
|
|
@@ -767,16 +823,16 @@ class Auth {
|
|
|
767
823
|
if (this.alepha.isBrowser()) {
|
|
768
824
|
const browser = this.alepha.get(ReactBrowserProvider);
|
|
769
825
|
const redirect = browser.transitioning ? window.location.origin + browser.transitioning.to : window.location.href;
|
|
770
|
-
window.location.href = `${this.
|
|
826
|
+
window.location.href = `${this.slugs.login}?redirect=${redirect}`;
|
|
771
827
|
if (browser.transitioning) {
|
|
772
828
|
throw new RedirectionError(browser.state.pathname);
|
|
773
829
|
}
|
|
774
830
|
return;
|
|
775
831
|
}
|
|
776
|
-
throw new RedirectionError(this.
|
|
832
|
+
throw new RedirectionError(this.slugs.login);
|
|
777
833
|
};
|
|
778
834
|
logout = () => {
|
|
779
|
-
window.location.href =
|
|
835
|
+
window.location.href = `${this.slugs.logout}?redirect=${encodeURIComponent(window.location.origin)}`;
|
|
780
836
|
};
|
|
781
837
|
}
|
|
782
838
|
|
|
@@ -906,8 +962,18 @@ const useRouter = () => {
|
|
|
906
962
|
|
|
907
963
|
const Link = (props) => {
|
|
908
964
|
React.useContext(RouterContext);
|
|
965
|
+
const to = typeof props.to === "string" ? props.to : props.to.options.path;
|
|
966
|
+
if (!to) {
|
|
967
|
+
return null;
|
|
968
|
+
}
|
|
969
|
+
const can = typeof props.to === "string" ? void 0 : props.to.options.can;
|
|
970
|
+
if (can && !can()) {
|
|
971
|
+
console.log("I cannot go to", to);
|
|
972
|
+
return null;
|
|
973
|
+
}
|
|
974
|
+
const name = typeof props.to === "string" ? void 0 : props.to.options.name;
|
|
909
975
|
const router = useRouter();
|
|
910
|
-
return /* @__PURE__ */ jsx("a", { ...router.createAnchorProps(
|
|
976
|
+
return /* @__PURE__ */ jsx("a", { ...router.createAnchorProps(to), ...props, children: props.children ?? name });
|
|
911
977
|
};
|
|
912
978
|
|
|
913
979
|
const useInject = (clazz) => {
|
|
@@ -915,7 +981,9 @@ const useInject = (clazz) => {
|
|
|
915
981
|
if (!ctx) {
|
|
916
982
|
throw new Error("useRouter must be used within a <RouterProvider>");
|
|
917
983
|
}
|
|
918
|
-
return ctx.alepha.get(clazz
|
|
984
|
+
return ctx.alepha.get(clazz, {
|
|
985
|
+
skipRegistration: true
|
|
986
|
+
});
|
|
919
987
|
};
|
|
920
988
|
|
|
921
989
|
const useClient = () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alepha/react",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -9,23 +9,24 @@
|
|
|
9
9
|
"./dist/index.js": "./dist/index.browser.js"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@alepha/core": "0.6.
|
|
13
|
-
"@alepha/security": "0.6.
|
|
14
|
-
"@alepha/server": "0.6.
|
|
12
|
+
"@alepha/core": "0.6.2",
|
|
13
|
+
"@alepha/security": "0.6.2",
|
|
14
|
+
"@alepha/server": "0.6.2",
|
|
15
|
+
"cheerio": "^1.0.0",
|
|
15
16
|
"openid-client": "^6.4.2",
|
|
16
17
|
"path-to-regexp": "^8.2.0",
|
|
17
|
-
"react-dom": "^
|
|
18
|
+
"react-dom": "^19.1.0"
|
|
18
19
|
},
|
|
19
20
|
"devDependencies": {
|
|
20
|
-
"@types/react": "^
|
|
21
|
-
"@types/react-dom": "^
|
|
22
|
-
"pkgroll": "^2.12.
|
|
23
|
-
"react": "^
|
|
24
|
-
"vitest": "^3.1.
|
|
21
|
+
"@types/react": "^19.1.2",
|
|
22
|
+
"@types/react-dom": "^19.1.3",
|
|
23
|
+
"pkgroll": "^2.12.2",
|
|
24
|
+
"react": "^19.1.0",
|
|
25
|
+
"vitest": "^3.1.2"
|
|
25
26
|
},
|
|
26
27
|
"peerDependencies": {
|
|
27
|
-
"@types/react": "^
|
|
28
|
-
"react": "^
|
|
28
|
+
"@types/react": "^19",
|
|
29
|
+
"react": "^19"
|
|
29
30
|
},
|
|
30
31
|
"scripts": {
|
|
31
32
|
"build": "pkgroll --clean-dist"
|
package/src/components/Link.tsx
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import type { AnchorHTMLAttributes } from "react";
|
|
3
|
-
import { RouterContext } from "../contexts/RouterContext";
|
|
4
|
-
import { useRouter } from "../hooks/useRouter";
|
|
5
|
-
|
|
6
|
-
export interface LinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
|
|
7
|
-
to: string;
|
|
8
|
-
children?: React.ReactNode;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const Link = (props: LinkProps) => {
|
|
12
|
-
React.useContext(RouterContext);
|
|
13
|
-
|
|
14
|
-
const router = useRouter();
|
|
15
|
-
return (
|
|
16
|
-
<a {...router.createAnchorProps(props.to)} {...props}>
|
|
17
|
-
{props.children}
|
|
18
|
-
</a>
|
|
19
|
-
);
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export default Link;
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from "react";
|
|
2
|
-
import { useContext, useEffect, useState } from "react";
|
|
3
|
-
import { RouterContext } from "../contexts/RouterContext";
|
|
4
|
-
import { RouterLayerContext } from "../contexts/RouterLayerContext";
|
|
5
|
-
|
|
6
|
-
export interface NestedViewProps {
|
|
7
|
-
children?: ReactNode;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Nested view component
|
|
12
|
-
*
|
|
13
|
-
* @param props
|
|
14
|
-
* @constructor
|
|
15
|
-
*/
|
|
16
|
-
const NestedView = (props: NestedViewProps) => {
|
|
17
|
-
const app = useContext(RouterContext);
|
|
18
|
-
const layer = useContext(RouterLayerContext);
|
|
19
|
-
const index = layer?.index ?? 0;
|
|
20
|
-
|
|
21
|
-
const [view, setView] = useState<ReactNode | undefined>(
|
|
22
|
-
app?.state.layers[index]?.element,
|
|
23
|
-
);
|
|
24
|
-
|
|
25
|
-
useEffect(() => {
|
|
26
|
-
if (app?.alepha.isBrowser()) {
|
|
27
|
-
return app?.router.on("end", (state) => {
|
|
28
|
-
setView(state.layers[index]?.element);
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
}, [app]);
|
|
32
|
-
|
|
33
|
-
return view ?? props.children ?? null;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
export default NestedView;
|
package/src/constants/SSID.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const SSID = "ssid";
|