@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
|
@@ -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"
|