@alepha/react 0.15.0 → 0.15.1
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/auth/index.browser.js +603 -242
- package/dist/auth/index.browser.js.map +1 -1
- package/dist/auth/index.d.ts +6 -6
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +1296 -922
- package/dist/auth/index.js.map +1 -1
- package/dist/core/index.d.ts +128 -128
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +20 -20
- package/dist/core/index.js.map +1 -1
- package/dist/form/index.d.ts +36 -36
- package/dist/form/index.d.ts.map +1 -1
- package/dist/form/index.js +15 -15
- package/dist/form/index.js.map +1 -1
- package/dist/head/index.browser.js +20 -0
- package/dist/head/index.browser.js.map +1 -1
- package/dist/head/index.d.ts +73 -65
- package/dist/head/index.d.ts.map +1 -1
- package/dist/head/index.js +20 -0
- package/dist/head/index.js.map +1 -1
- package/dist/i18n/index.d.ts +37 -37
- package/dist/i18n/index.d.ts.map +1 -1
- package/dist/i18n/index.js.map +1 -1
- package/dist/router/index.browser.js +605 -244
- package/dist/router/index.browser.js.map +1 -1
- package/dist/router/index.d.ts +539 -550
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js +1296 -922
- package/dist/router/index.js.map +1 -1
- package/dist/websocket/index.d.ts +38 -38
- package/dist/websocket/index.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/auth/__tests__/$auth.spec.ts +162 -147
- package/src/auth/index.ts +9 -3
- package/src/auth/services/ReactAuth.ts +15 -5
- package/src/core/hooks/useAction.ts +1 -2
- package/src/core/index.ts +4 -4
- package/src/form/errors/FormValidationError.ts +4 -6
- package/src/form/hooks/useFormState.ts +1 -1
- package/src/form/index.ts +1 -1
- package/src/form/services/FormModel.ts +31 -25
- package/src/head/helpers/SeoExpander.ts +2 -1
- package/src/head/hooks/useHead.spec.tsx +2 -2
- package/src/head/index.browser.ts +2 -2
- package/src/head/index.ts +4 -4
- package/src/head/interfaces/Head.ts +15 -3
- package/src/head/primitives/$head.ts +2 -5
- package/src/head/providers/BrowserHeadProvider.ts +55 -0
- package/src/head/providers/HeadProvider.ts +4 -1
- package/src/i18n/__tests__/integration.spec.tsx +1 -1
- package/src/i18n/components/Localize.spec.tsx +2 -2
- package/src/i18n/hooks/useI18n.browser.spec.tsx +2 -2
- package/src/i18n/index.ts +1 -1
- package/src/i18n/primitives/$dictionary.ts +1 -1
- package/src/i18n/providers/I18nProvider.spec.ts +1 -1
- package/src/i18n/providers/I18nProvider.ts +1 -1
- package/src/router/__tests__/page-head-browser.browser.spec.ts +5 -1
- package/src/router/__tests__/page-head.spec.ts +11 -7
- package/src/router/__tests__/seo-head.spec.ts +7 -3
- package/src/router/atoms/ssrManifestAtom.ts +2 -11
- package/src/router/components/ErrorViewer.tsx +626 -167
- package/src/router/components/Link.tsx +4 -2
- package/src/router/components/NestedView.tsx +7 -9
- package/src/router/components/NotFound.tsx +2 -2
- package/src/router/hooks/useQueryParams.ts +1 -1
- package/src/router/hooks/useRouter.ts +1 -1
- package/src/router/hooks/useRouterState.ts +1 -1
- package/src/router/index.browser.ts +10 -11
- package/src/router/index.shared.ts +7 -7
- package/src/router/index.ts +10 -7
- package/src/router/primitives/$page.browser.spec.tsx +6 -1
- package/src/router/primitives/$page.spec.tsx +7 -1
- package/src/router/primitives/$page.ts +5 -9
- package/src/router/providers/ReactBrowserProvider.ts +17 -6
- package/src/router/providers/ReactBrowserRouterProvider.ts +1 -1
- package/src/router/providers/ReactPageProvider.ts +4 -3
- package/src/router/providers/ReactServerProvider.ts +29 -37
- package/src/router/providers/ReactServerTemplateProvider.ts +300 -137
- package/src/router/providers/SSRManifestProvider.ts +17 -60
- package/src/router/services/ReactPageService.ts +4 -1
- package/src/router/services/ReactRouter.ts +6 -5
|
@@ -7,48 +7,48 @@ import { Static } from "alepha";
|
|
|
7
7
|
*/
|
|
8
8
|
interface UseRoomOptions<TClient extends TWSObject, TServer extends TWSObject> {
|
|
9
9
|
/**
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
* Room ID to connect to
|
|
11
|
+
*/
|
|
12
12
|
roomId: string;
|
|
13
13
|
/**
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
* Channel primitive defining the schemas
|
|
15
|
+
*/
|
|
16
16
|
channel: ChannelPrimitive<TClient, TServer>;
|
|
17
17
|
/**
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
* Handler for incoming messages from the server
|
|
19
|
+
*/
|
|
20
20
|
handler: (message: Static<TClient>) => void;
|
|
21
21
|
/**
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
* Optional WebSocket URL override
|
|
23
|
+
* Defaults to auto-detected URL based on window.location
|
|
24
|
+
*/
|
|
25
25
|
url?: string;
|
|
26
26
|
/**
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
* Enable automatic reconnection on disconnect
|
|
28
|
+
* @default true
|
|
29
|
+
*/
|
|
30
30
|
autoReconnect?: boolean;
|
|
31
31
|
/**
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
* Reconnection interval in milliseconds
|
|
33
|
+
* @default 3000
|
|
34
|
+
*/
|
|
35
35
|
reconnectInterval?: number;
|
|
36
36
|
/**
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
* Maximum reconnection attempts (-1 for infinite)
|
|
38
|
+
* @default 10
|
|
39
|
+
*/
|
|
40
40
|
maxReconnectAttempts?: number;
|
|
41
41
|
/**
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
* Called when connection is established
|
|
43
|
+
*/
|
|
44
44
|
onConnect?: () => void;
|
|
45
45
|
/**
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
* Called when connection is closed
|
|
47
|
+
*/
|
|
48
48
|
onDisconnect?: () => void;
|
|
49
49
|
/**
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
* Called on connection error
|
|
51
|
+
*/
|
|
52
52
|
onError?: (error: Error) => void;
|
|
53
53
|
}
|
|
54
54
|
/**
|
|
@@ -56,32 +56,32 @@ interface UseRoomOptions<TClient extends TWSObject, TServer extends TWSObject> {
|
|
|
56
56
|
*/
|
|
57
57
|
interface UseRoomReturn<TServer extends TWSObject> {
|
|
58
58
|
/**
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
* Send a message to the server
|
|
60
|
+
*/
|
|
61
61
|
send: (message: Static<TServer>) => Promise<void>;
|
|
62
62
|
/**
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
* Whether the connection is established
|
|
64
|
+
*/
|
|
65
65
|
isConnected: boolean;
|
|
66
66
|
/**
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
* Whether the connection is in progress
|
|
68
|
+
*/
|
|
69
69
|
isConnecting: boolean;
|
|
70
70
|
/**
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
* Whether there was an error
|
|
72
|
+
*/
|
|
73
73
|
isError: boolean;
|
|
74
74
|
/**
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
* The error object if any
|
|
76
|
+
*/
|
|
77
77
|
error?: Error;
|
|
78
78
|
/**
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
* Manually reconnect
|
|
80
|
+
*/
|
|
81
81
|
reconnect: () => void;
|
|
82
82
|
/**
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
* Manually disconnect
|
|
84
|
+
*/
|
|
85
85
|
disconnect: () => void;
|
|
86
86
|
}
|
|
87
87
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/websocket/hooks/useRoom.tsx"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/websocket/hooks/useRoom.tsx"],"mappings":";;;;;;AASA;UAAiB,cAAA,iBACC,SAAA,kBACA,SAAA;EAFa;;;EAO7B,MAAA;EAKmC;;;EAAnC,OAAA,EAAS,gBAAA,CAAiB,OAAA,EAAS,OAAA;EA4CjB;;;EAvClB,OAAA,GAAU,OAAA,EAAS,MAAA,CAAO,OAAA;EAhBV;;;;EAsBhB,GAAA;EAXS;;;;EAiBT,aAAA;EAZ0B;;;;EAkB1B,iBAAA;EAMA;;;;EAAA,oBAAA;EAeW;;;EAVX,SAAA;EAgB4B;;;EAX5B,YAAA;EAegB;;;EAVhB,OAAA,IAAW,KAAA,EAAO,KAAA;AAAA;;;;UAMH,aAAA,iBAA8B,SAAA;EAItB;;;EAAvB,IAAA,GAAO,OAAA,EAAS,MAAA,CAAO,OAAA,MAAa,OAAA;EAUpC;;;EALA,WAAA;EAoBA;;;EAfA,YAAA;EAkDW;;;EA7CX,OAAA;EA6CiE;;;EAxCjE,KAAA,GAAQ,KAAA;EA2CO;;;EAtCf,SAAA;EAmCsB;;;EA9BtB,UAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA8BW,OAAA,mBAA2B,SAAA,kBAA2B,SAAA,EACjE,OAAA,EAAS,cAAA,CAAe,OAAA,EAAS,OAAA,GACjC,IAAA,gBACC,aAAA,CAAc,OAAA"}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@alepha/react",
|
|
3
3
|
"description": "React components and hooks for building Alepha applications.",
|
|
4
4
|
"author": "Nicolas Foures",
|
|
5
|
-
"version": "0.15.
|
|
5
|
+
"version": "0.15.1",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"engines": {
|
|
8
8
|
"node": ">=22.0.0"
|
|
@@ -20,19 +20,19 @@
|
|
|
20
20
|
"react-dom": "^19"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@biomejs/biome": "^2.3.
|
|
23
|
+
"@biomejs/biome": "^2.3.12",
|
|
24
24
|
"@testing-library/dom": "^10.4.1",
|
|
25
|
-
"@testing-library/react": "^16.3.
|
|
25
|
+
"@testing-library/react": "^16.3.2",
|
|
26
26
|
"@types/react": "^19",
|
|
27
27
|
"@types/react-dom": "^19",
|
|
28
|
-
"alepha": "0.15.
|
|
28
|
+
"alepha": "0.15.1",
|
|
29
29
|
"jsdom": "^27.4.0",
|
|
30
30
|
"react": "^19.2.3",
|
|
31
31
|
"typescript": "^5.9.3",
|
|
32
|
-
"vitest": "^4.0.
|
|
32
|
+
"vitest": "^4.0.18"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"alepha": "0.15.
|
|
35
|
+
"alepha": "0.15.1",
|
|
36
36
|
"react": "^19"
|
|
37
37
|
},
|
|
38
38
|
"scripts": {
|
|
@@ -3,185 +3,200 @@ import { Alepha } from "alepha";
|
|
|
3
3
|
import { DateTimeProvider } from "alepha/datetime";
|
|
4
4
|
import { $issuer, AlephaSecurity } from "alepha/security";
|
|
5
5
|
import { AlephaServer, HttpClient, ServerProvider } from "alepha/server";
|
|
6
|
-
import { $client } from "alepha/server/links";
|
|
7
|
-
import { describe, test } from "vitest";
|
|
8
6
|
import {
|
|
9
7
|
$auth,
|
|
10
8
|
alephaServerAuthRoutes,
|
|
11
9
|
type TokenResponse,
|
|
12
10
|
tokenResponseSchema,
|
|
13
|
-
tokensSchema
|
|
11
|
+
tokensSchema,
|
|
14
12
|
} from "alepha/server/auth";
|
|
15
|
-
import {
|
|
13
|
+
import { $client } from "alepha/server/links";
|
|
14
|
+
import { describe, test } from "vitest";
|
|
15
|
+
import { ReactAuth, type ReactAuthProvider } from "../index.ts";
|
|
16
16
|
|
|
17
17
|
describe("$auth", () => {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
},
|
|
34
|
-
],
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
auth = $auth({
|
|
38
|
-
issuer: this.issuer,
|
|
39
|
-
credentials: {
|
|
40
|
-
account: () => user,
|
|
18
|
+
const user = {
|
|
19
|
+
id: randomUUID(),
|
|
20
|
+
name: "John Doe",
|
|
21
|
+
username: "john",
|
|
22
|
+
password: "***",
|
|
23
|
+
roles: ["admin"],
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
class App {
|
|
27
|
+
issuer = $issuer({
|
|
28
|
+
secret: "my-secret-key",
|
|
29
|
+
roles: [
|
|
30
|
+
{
|
|
31
|
+
name: "admin",
|
|
32
|
+
permissions: [{ name: "*" }],
|
|
41
33
|
},
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
34
|
+
],
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
auth = $auth({
|
|
38
|
+
issuer: this.issuer,
|
|
39
|
+
credentials: {
|
|
40
|
+
account: () => user,
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
api = $client<ReactAuthProvider>();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const userinfo = (alepha: Alepha, token?: string) =>
|
|
48
|
+
alepha
|
|
49
|
+
.inject(HttpClient)
|
|
50
|
+
.fetch(
|
|
51
|
+
`${alepha.inject(ServerProvider).hostname}${alephaServerAuthRoutes.userinfo}`,
|
|
52
|
+
{
|
|
53
|
+
method: "GET",
|
|
54
|
+
headers: {
|
|
55
|
+
authorization: `Bearer ${token}`,
|
|
57
56
|
},
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
57
|
+
},
|
|
58
|
+
)
|
|
59
|
+
.then((it) => it.data);
|
|
60
|
+
|
|
61
|
+
const login = (alepha: Alepha) =>
|
|
62
|
+
alepha
|
|
63
|
+
.inject(HttpClient)
|
|
64
|
+
.fetch(
|
|
65
|
+
`${alepha.inject(ServerProvider).hostname}${alephaServerAuthRoutes.token}?provider=auth`,
|
|
66
|
+
{
|
|
67
|
+
method: "POST",
|
|
68
|
+
body: JSON.stringify({
|
|
69
|
+
username: user.username,
|
|
70
|
+
password: user.password,
|
|
71
|
+
}),
|
|
72
|
+
schema: {
|
|
73
|
+
response: tokenResponseSchema,
|
|
75
74
|
},
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const refresh = (alepha: Alepha, tokens: TokenResponse) =>
|
|
79
|
+
alepha
|
|
80
|
+
.inject(HttpClient)
|
|
81
|
+
.fetch(
|
|
82
|
+
`${alepha.inject(ServerProvider).hostname}${alephaServerAuthRoutes.refresh}?provider=auth`,
|
|
83
|
+
{
|
|
84
|
+
method: "POST",
|
|
85
|
+
body: JSON.stringify({
|
|
86
|
+
refresh_token: tokens.refresh_token!,
|
|
87
|
+
access_token: tokens.access_token,
|
|
88
|
+
}),
|
|
89
|
+
schema: {
|
|
90
|
+
response: tokensSchema,
|
|
92
91
|
},
|
|
93
|
-
|
|
92
|
+
},
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
test("should login with credentials", async ({ expect }) => {
|
|
96
|
+
const alepha = Alepha.create()
|
|
97
|
+
.with(AlephaServer)
|
|
98
|
+
.with(AlephaSecurity)
|
|
99
|
+
.with(App);
|
|
100
|
+
const auth = alepha.inject(ReactAuth);
|
|
101
|
+
await alepha.start();
|
|
102
|
+
|
|
103
|
+
expect(auth.user).toBeUndefined();
|
|
104
|
+
await auth.login("auth", {
|
|
105
|
+
username: user.username,
|
|
106
|
+
password: user.password,
|
|
107
|
+
hostname: alepha.inject(ServerProvider).hostname,
|
|
108
|
+
});
|
|
109
|
+
expect(auth.user).toEqual({
|
|
110
|
+
id: user.id,
|
|
111
|
+
name: user.name,
|
|
112
|
+
roles: user.roles,
|
|
113
|
+
username: user.username,
|
|
114
|
+
});
|
|
115
|
+
});
|
|
94
116
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
117
|
+
test("should get userinfo", async ({ expect }) => {
|
|
118
|
+
const alepha = Alepha.create()
|
|
119
|
+
.with(AlephaServer)
|
|
120
|
+
.with(AlephaSecurity)
|
|
121
|
+
.with(App);
|
|
122
|
+
await alepha.start();
|
|
99
123
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
hostname: alepha.inject(ServerProvider).hostname,
|
|
105
|
-
});
|
|
106
|
-
expect(auth.user).toEqual({
|
|
124
|
+
const { data: tokens } = await login(alepha);
|
|
125
|
+
|
|
126
|
+
expect(await userinfo(alepha, tokens.access_token)).toEqual({
|
|
127
|
+
user: {
|
|
107
128
|
id: user.id,
|
|
108
129
|
name: user.name,
|
|
109
130
|
roles: user.roles,
|
|
110
131
|
username: user.username,
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const { data: tokens } = await login(alepha);
|
|
119
|
-
|
|
120
|
-
expect(await userinfo(alepha, tokens.access_token)).toEqual({
|
|
121
|
-
user: {
|
|
122
|
-
id: user.id,
|
|
123
|
-
name: user.name,
|
|
124
|
-
roles: user.roles,
|
|
125
|
-
username: user.username,
|
|
126
|
-
sessionId: expect.any(String),
|
|
127
|
-
},
|
|
128
|
-
api: {
|
|
129
|
-
prefix: "/api",
|
|
130
|
-
links: [],
|
|
131
|
-
},
|
|
132
|
-
});
|
|
132
|
+
sessionId: expect.any(String),
|
|
133
|
+
},
|
|
134
|
+
api: {
|
|
135
|
+
prefix: "/api",
|
|
136
|
+
links: [],
|
|
137
|
+
},
|
|
133
138
|
});
|
|
139
|
+
});
|
|
134
140
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
141
|
+
test("should reject expired token", async ({ expect }) => {
|
|
142
|
+
const alepha = Alepha.create()
|
|
143
|
+
.with(AlephaServer)
|
|
144
|
+
.with(AlephaSecurity)
|
|
145
|
+
.with(App);
|
|
146
|
+
await alepha.start();
|
|
138
147
|
|
|
139
|
-
|
|
148
|
+
const { data: tokens } = await login(alepha);
|
|
140
149
|
|
|
141
|
-
|
|
150
|
+
await alepha.inject(DateTimeProvider).travel(1, "hour");
|
|
142
151
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
});
|
|
152
|
+
expect(await userinfo(alepha, tokens.access_token)).toEqual({
|
|
153
|
+
api: {
|
|
154
|
+
prefix: "/api",
|
|
155
|
+
links: [],
|
|
156
|
+
},
|
|
149
157
|
});
|
|
158
|
+
});
|
|
150
159
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
160
|
+
test("should refresh expired token", async ({ expect }) => {
|
|
161
|
+
const alepha = Alepha.create()
|
|
162
|
+
.with(AlephaServer)
|
|
163
|
+
.with(AlephaSecurity)
|
|
164
|
+
.with(App);
|
|
165
|
+
await alepha.start();
|
|
154
166
|
|
|
155
|
-
|
|
167
|
+
const { data: tokens } = await login(alepha);
|
|
156
168
|
|
|
157
|
-
|
|
169
|
+
await alepha.inject(DateTimeProvider).travel(1, "hour");
|
|
158
170
|
|
|
159
|
-
|
|
171
|
+
const { data: tokens2 } = await refresh(alepha, tokens);
|
|
160
172
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
});
|
|
173
|
+
expect(await userinfo(alepha, tokens2.access_token)).toEqual({
|
|
174
|
+
user: {
|
|
175
|
+
id: user.id,
|
|
176
|
+
name: user.name,
|
|
177
|
+
roles: user.roles,
|
|
178
|
+
username: user.username,
|
|
179
|
+
},
|
|
180
|
+
api: {
|
|
181
|
+
prefix: "/api",
|
|
182
|
+
links: [],
|
|
183
|
+
},
|
|
173
184
|
});
|
|
185
|
+
});
|
|
174
186
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
187
|
+
test("should reject expired refresh token", async ({ expect }) => {
|
|
188
|
+
const alepha = Alepha.create()
|
|
189
|
+
.with(AlephaServer)
|
|
190
|
+
.with(AlephaSecurity)
|
|
191
|
+
.with(App);
|
|
192
|
+
await alepha.start();
|
|
178
193
|
|
|
179
|
-
|
|
194
|
+
const { data: tokens } = await login(alepha);
|
|
180
195
|
|
|
181
|
-
|
|
196
|
+
await alepha.inject(DateTimeProvider).travel(40, "days");
|
|
182
197
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
198
|
+
await expect(refresh(alepha, tokens)).rejects.toThrowError(
|
|
199
|
+
"Failed to refresh access token using the refresh token (issuer)",
|
|
200
|
+
);
|
|
201
|
+
});
|
|
187
202
|
});
|
package/src/auth/index.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { AlephaReact } from "@alepha/react";
|
|
2
2
|
import { $module } from "alepha";
|
|
3
3
|
import type { UserAccount } from "alepha/security";
|
|
4
|
-
import { ReactAuthProvider } from "./providers/ReactAuthProvider.ts";
|
|
5
|
-
import { ReactAuth } from "./services/ReactAuth.ts";
|
|
6
4
|
import { $auth, AlephaServerAuth } from "alepha/server/auth";
|
|
7
5
|
import { AlephaServerLinks } from "alepha/server/links";
|
|
6
|
+
import { ReactAuthProvider } from "./providers/ReactAuthProvider.ts";
|
|
7
|
+
import { ReactAuth } from "./services/ReactAuth.ts";
|
|
8
8
|
|
|
9
9
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
10
10
|
|
|
@@ -30,5 +30,11 @@ declare module "@alepha/react/router" {
|
|
|
30
30
|
export const AlephaReactAuth = $module({
|
|
31
31
|
name: "alepha.react.auth",
|
|
32
32
|
primitives: [$auth],
|
|
33
|
-
services: [
|
|
33
|
+
services: [
|
|
34
|
+
AlephaReact,
|
|
35
|
+
AlephaServerLinks,
|
|
36
|
+
AlephaServerAuth,
|
|
37
|
+
ReactAuthProvider,
|
|
38
|
+
ReactAuth,
|
|
39
|
+
],
|
|
34
40
|
});
|
|
@@ -3,7 +3,12 @@ import { $hook, $inject, Alepha } from "alepha";
|
|
|
3
3
|
import { $logger } from "alepha/logger";
|
|
4
4
|
import type { UserAccountToken } from "alepha/security";
|
|
5
5
|
import { HttpClient } from "alepha/server";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
alephaServerAuthRoutes,
|
|
8
|
+
type Tokens,
|
|
9
|
+
tokenResponseSchema,
|
|
10
|
+
userinfoResponseSchema,
|
|
11
|
+
} from "alepha/server/auth";
|
|
7
12
|
import { LinkProvider } from "alepha/server/links";
|
|
8
13
|
|
|
9
14
|
/**
|
|
@@ -46,9 +51,12 @@ export class ReactAuth {
|
|
|
46
51
|
}
|
|
47
52
|
|
|
48
53
|
public async ping() {
|
|
49
|
-
const { data } = await this.httpClient.fetch(
|
|
50
|
-
|
|
51
|
-
|
|
54
|
+
const { data } = await this.httpClient.fetch(
|
|
55
|
+
alephaServerAuthRoutes.userinfo,
|
|
56
|
+
{
|
|
57
|
+
schema: { response: userinfoResponseSchema },
|
|
58
|
+
},
|
|
59
|
+
);
|
|
52
60
|
|
|
53
61
|
this.alepha.store.set("alepha.server.request.apiLinks", data.api);
|
|
54
62
|
this.alepha.store.set("alepha.server.request.user", data.user);
|
|
@@ -75,7 +83,9 @@ export class ReactAuth {
|
|
|
75
83
|
[extra: string]: any;
|
|
76
84
|
},
|
|
77
85
|
): Promise<Tokens> {
|
|
78
|
-
const realmParam = options.realm
|
|
86
|
+
const realmParam = options.realm
|
|
87
|
+
? `&realm=${encodeURIComponent(options.realm)}`
|
|
88
|
+
: "";
|
|
79
89
|
|
|
80
90
|
if (options.username || options.password) {
|
|
81
91
|
const { data } = await this.httpClient.fetch(
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Async } from "alepha";
|
|
1
2
|
import {
|
|
2
3
|
DateTimeProvider,
|
|
3
4
|
type DurationLike,
|
|
@@ -13,7 +14,6 @@ import {
|
|
|
13
14
|
} from "react";
|
|
14
15
|
import { useAlepha } from "./useAlepha.ts";
|
|
15
16
|
import { useInject } from "./useInject.ts";
|
|
16
|
-
import type { Async } from "alepha";
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Hook for handling async actions with automatic error handling and event emission.
|
|
@@ -207,7 +207,6 @@ export function useAction<Args extends any[], Result = void>(
|
|
|
207
207
|
await options.onSuccess(result);
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
|
|
211
210
|
return result;
|
|
212
211
|
} catch (err) {
|
|
213
212
|
// Ignore abort errors
|
package/src/core/index.ts
CHANGED
|
@@ -2,17 +2,17 @@ import { $module } from "alepha";
|
|
|
2
2
|
|
|
3
3
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
4
4
|
|
|
5
|
-
export { default as ClientOnly } from "./components/ClientOnly.tsx";
|
|
6
5
|
export type * from "./components/ClientOnly.tsx";
|
|
7
|
-
export { default as
|
|
6
|
+
export { default as ClientOnly } from "./components/ClientOnly.tsx";
|
|
8
7
|
export type * from "./components/ErrorBoundary.tsx";
|
|
9
|
-
export
|
|
8
|
+
export { default as ErrorBoundary } from "./components/ErrorBoundary.tsx";
|
|
10
9
|
export * from "./contexts/AlephaContext.ts";
|
|
10
|
+
export * from "./contexts/AlephaProvider.tsx";
|
|
11
11
|
export * from "./hooks/useAction.ts";
|
|
12
12
|
export * from "./hooks/useAlepha.ts";
|
|
13
|
+
export * from "./hooks/useClient.ts";
|
|
13
14
|
export * from "./hooks/useEvents.ts";
|
|
14
15
|
export * from "./hooks/useInject.ts";
|
|
15
|
-
export * from "./hooks/useClient.ts";
|
|
16
16
|
export * from "./hooks/useStore.ts";
|
|
17
17
|
|
|
18
18
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
@@ -3,12 +3,10 @@ import { TypeBoxError } from "alepha";
|
|
|
3
3
|
export class FormValidationError extends TypeBoxError {
|
|
4
4
|
readonly name = "ValidationError";
|
|
5
5
|
|
|
6
|
-
constructor(
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
) {
|
|
6
|
+
constructor(options: {
|
|
7
|
+
message: string;
|
|
8
|
+
path: string;
|
|
9
|
+
}) {
|
|
12
10
|
super({
|
|
13
11
|
message: options.message,
|
|
14
12
|
instancePath: options.path,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useAlepha } from "@alepha/react";
|
|
2
|
-
import type { FormModel } from "../services/FormModel.ts";
|
|
3
2
|
import { type TObject, TypeBoxError } from "alepha";
|
|
4
3
|
import { useEffect, useState } from "react";
|
|
4
|
+
import type { FormModel } from "../services/FormModel.ts";
|
|
5
5
|
|
|
6
6
|
export interface UseFormStateReturn {
|
|
7
7
|
loading: boolean;
|
package/src/form/index.ts
CHANGED
|
@@ -3,10 +3,10 @@ import { $module } from "alepha";
|
|
|
3
3
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
4
4
|
|
|
5
5
|
export { default as FormState } from "./components/FormState.tsx";
|
|
6
|
+
export * from "./errors/FormValidationError.ts";
|
|
6
7
|
export * from "./hooks/useForm.ts";
|
|
7
8
|
export * from "./hooks/useFormState.ts";
|
|
8
9
|
export * from "./services/FormModel.ts";
|
|
9
|
-
export * from "./errors/FormValidationError.ts";
|
|
10
10
|
|
|
11
11
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
12
12
|
|