5htp-core 0.1.2 → 0.2.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/changelog.md +5 -0
- package/doc/TODO.md +71 -0
- package/package.json +5 -4
- package/src/client/{App.tsx → app/component.tsx} +15 -11
- package/src/client/app/index.ts +128 -0
- package/src/client/app/service.ts +34 -0
- package/src/client/app.tsconfig.json +0 -4
- package/src/client/assets/css/components.less +52 -0
- package/src/client/assets/css/core.less +7 -28
- package/src/client/assets/css/theme.less +1 -1
- package/src/client/assets/css/{borders.less → utils/borders.less} +0 -0
- package/src/client/assets/css/{layouts.less → utils/layouts.less} +0 -0
- package/src/client/assets/css/{medias.less → utils/medias.less} +14 -1
- package/src/client/assets/css/{sizing.less → utils/sizing.less} +0 -0
- package/src/client/assets/css/{spacing.less → utils/spacing.less} +0 -0
- package/src/client/components/Card/index.tsx +13 -7
- package/src/client/components/Dialog/Manager.tsx +41 -14
- package/src/client/components/Dialog/index.less +2 -4
- package/src/client/components/Form/index.tsx +1 -1
- package/src/client/components/Row/index.less +0 -2
- package/src/client/components/Table/index.tsx +3 -2
- package/src/client/components/button.tsx +2 -2
- package/src/client/components/containers/Popover/index.tsx +1 -1
- package/src/client/components/containers/champs.less +0 -2
- package/src/client/components/data/spintext/index.tsx +1 -1
- package/src/client/components/dropdown/index.tsx +1 -1
- package/src/client/components/index.ts +23 -0
- package/src/client/components/input/BaseV2/index.less +0 -2
- package/src/client/components/input/BaseV2/index.tsx +1 -1
- package/src/client/components/input/Date/index.less +0 -2
- package/src/client/components/input/Periode/index.less +0 -2
- package/src/client/components/input/Radio/index.less +0 -2
- package/src/client/components/input/UploadImage/index.less +0 -2
- package/src/client/components/input/UploadImage/index.tsx +1 -1
- package/src/client/hooks/index.ts +5 -0
- package/src/client/hooks/useState/index.tsx +2 -2
- package/src/client/hooks.ts +22 -0
- package/src/client/index.ts +5 -0
- package/src/client/pages/_layout/landing/index.tsx +0 -2
- package/src/client/pages/_messages/400.tsx +2 -2
- package/src/client/pages/_messages/401.tsx +2 -2
- package/src/client/pages/_messages/403.tsx +2 -2
- package/src/client/pages/_messages/404.tsx +2 -2
- package/src/client/pages/_messages/500.tsx +2 -2
- package/src/client/pages/bug.tsx +1 -1
- package/src/client/pages/useHeader.tsx +1 -1
- package/src/client/{context/captcha.ts → services/captcha/index.ts} +0 -0
- package/src/client/services/metrics/index.ts +37 -0
- package/src/client/{router → services/router/components}/Link.tsx +1 -1
- package/src/client/services/router/components/Page.tsx +59 -0
- package/src/client/{router/component.tsx → services/router/components/router.tsx} +52 -74
- package/src/client/services/router/index.tsx +453 -0
- package/src/client/services/router/request/api.ts +227 -0
- package/src/client/{router → services/router}/request/history.ts +0 -0
- package/src/client/services/router/request/index.ts +52 -0
- package/src/client/services/router/response/index.tsx +107 -0
- package/src/client/services/router/response/page.ts +90 -0
- package/src/client/{context/socket.ts → services/socket/index.ts} +2 -2
- package/src/client/utils/dom.ts +1 -1
- package/src/common/app/index.ts +9 -0
- package/src/common/data/chaines/index.ts +9 -6
- package/src/common/data/input/validate.ts +3 -166
- package/src/common/data/objets.ts +25 -0
- package/src/common/data/tableaux.ts +8 -0
- package/src/common/errors/index.ts +3 -1
- package/src/common/router/index.ts +67 -88
- package/src/common/router/layouts.ts +50 -0
- package/src/common/router/register.ts +62 -0
- package/src/common/router/request/api.ts +72 -0
- package/src/common/router/request/index.ts +31 -0
- package/src/common/router/{response.ts → response/index.ts} +9 -13
- package/src/common/router/response/page.ts +46 -54
- package/src/common/validation/index.ts +3 -0
- package/src/common/validation/schema.ts +185 -0
- package/src/common/validation/validator.ts +95 -0
- package/src/common/validation/validators.ts +313 -0
- package/src/server/app/config.ts +9 -27
- package/src/server/app/index.ts +81 -124
- package/src/server/app/service.ts +98 -0
- package/src/server/app.tsconfig.json +0 -8
- package/src/server/index.ts +5 -0
- package/src/server/patch.ts +0 -6
- package/src/server/{data/Cache.ts → services/cache/index.ts} +79 -47
- package/src/server/services/console/bugReporter.ts +26 -16
- package/src/server/services/console/index.ts +59 -51
- package/src/server/services/cron/index.ts +12 -26
- package/src/server/services/database/bucket.ts +40 -0
- package/src/server/services/database/connection.ts +213 -80
- package/src/server/services/database/datatypes.ts +63 -40
- package/src/server/services/database/debug.ts +20 -0
- package/src/server/services/database/index.ts +295 -272
- package/src/server/services/database/metas.ts +246 -135
- package/src/server/services/database/stats.ts +151 -126
- package/src/server/services/email/index.ts +30 -62
- package/src/server/services/email/transporter.ts +38 -0
- package/src/server/services/{router/request/services → metrics}/detect.ts +8 -10
- package/src/server/services/{router/request/services/tracking.ts → metrics/index.ts} +68 -45
- package/src/server/services/{http → router/http}/index.ts +28 -70
- package/src/server/services/{http → router/http}/multipart.ts +0 -0
- package/src/server/services/{http → router/http}/session.ts.old +0 -0
- package/src/server/services/router/index.ts +273 -202
- package/src/server/services/router/request/api.ts +76 -0
- package/src/server/services/router/request/index.ts +16 -97
- package/src/server/services/router/request/service.ts +21 -0
- package/src/server/services/router/response/index.ts +131 -65
- package/src/server/services/router/response/{filter → mask}/Filter.ts +0 -0
- package/src/server/services/router/response/{filter → mask}/index.ts +0 -2
- package/src/server/services/router/response/{filter → mask}/selecteurs.ts +0 -0
- package/src/server/services/router/response/page/document.tsx +194 -0
- package/src/server/services/router/response/page/index.tsx +157 -0
- package/src/server/{libs/pages → services/router/response/page}/schemaGenerator.ts +0 -0
- package/src/server/services/router/service.ts +48 -0
- package/src/server/services/schema/index.ts +47 -0
- package/src/server/services/schema/request.ts +55 -0
- package/src/server/services/schema/router.ts +33 -0
- package/src/server/services/socket/index.ts +38 -43
- package/src/server/services/socket/scope.ts +6 -4
- package/src/server/services/users/index.ts +203 -0
- package/src/server/services/{auth/base.ts → users/old.ts} +28 -112
- package/src/server/services/users/router/index.ts +72 -0
- package/src/server/services/users/router/request.ts +49 -0
- package/src/server/{data → services_old}/SocketClient.ts +0 -0
- package/src/server/{data/Token.olg.ts → services_old/Token.old.ts} +0 -0
- package/src/server/{data → services_old}/aes.ts +0 -0
- package/src/types/aliases.d.ts +43 -2
- package/templates/composant.tsx +1 -1
- package/templates/modal.tsx +1 -1
- package/templates/page.tsx +1 -1
- package/tsconfig.common.json +0 -4
- package/src/client/assets/css/components/components.less +0 -31
- package/src/client/context/api.ts +0 -92
- package/src/client/context/index.ts +0 -246
- package/src/client/index.tsx +0 -129
- package/src/client/router/index.ts +0 -286
- package/src/client/router/request/index.ts +0 -106
- package/src/client/router/response/index.ts +0 -38
- package/src/client/router/route.ts +0 -75
- package/src/common/data/input/validators/basic.ts +0 -299
- package/src/common/data/input/validators/build.ts +0 -63
- package/src/common/router/request.ts +0 -83
- package/src/server/data/ApiClient.ts +0 -119
- package/src/server/data/input.ts +0 -41
- package/src/server/libs/pages/document.static.tsx +0 -41
- package/src/server/libs/pages/document.tsx +0 -203
- package/src/server/libs/pages/render.tsx +0 -90
- package/src/server/routes/auth.ts +0 -151
- package/src/server/services/redis/index.ts +0 -71
- package/src/server/services/router/request/services/auth.ts +0 -177
|
@@ -4,21 +4,21 @@
|
|
|
4
4
|
// Npm
|
|
5
5
|
import React from 'react';
|
|
6
6
|
|
|
7
|
-
//
|
|
8
|
-
import useContext from '
|
|
9
|
-
|
|
7
|
+
// Core
|
|
8
|
+
import useContext from '@/client/context';
|
|
9
|
+
|
|
10
|
+
// Specific
|
|
11
|
+
import type Router from '..';
|
|
12
|
+
import PageComponent from './Page';
|
|
13
|
+
import ClientRequest from '../request';
|
|
14
|
+
import { history, location, Update } from '../request/history';
|
|
10
15
|
//import initTooltips from '@client/components/Donnees/Tooltip';
|
|
11
|
-
|
|
12
|
-
// Navigation
|
|
13
|
-
import router from '.';
|
|
14
|
-
import { history, location, Update } from './request/history';
|
|
16
|
+
import type Page from '../response/page';
|
|
15
17
|
|
|
16
18
|
/*----------------------------------
|
|
17
19
|
- TYPES
|
|
18
20
|
----------------------------------*/
|
|
19
21
|
|
|
20
|
-
import type PageResponse from '../../common/router/response/page';
|
|
21
|
-
|
|
22
22
|
export type PropsPage<TParams extends { [cle: string]: unknown }> = TParams & {
|
|
23
23
|
data: {[cle: string]: unknown}
|
|
24
24
|
}
|
|
@@ -27,52 +27,20 @@ export type PropsPage<TParams extends { [cle: string]: unknown }> = TParams & {
|
|
|
27
27
|
- PAGE STATE
|
|
28
28
|
----------------------------------*/
|
|
29
29
|
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
const gui = useContext();
|
|
33
|
-
|
|
34
|
-
const [data, setData] = React.useState<{[k: string]: any} | null>( page.loading ? null : page.data );
|
|
35
|
-
page.setAllData = setData;
|
|
36
|
-
|
|
37
|
-
React.useEffect(() => {
|
|
38
|
-
|
|
39
|
-
if (data === null && isCurrent)
|
|
40
|
-
page.fetchData().then( loadedData => {
|
|
41
|
-
page.loading = false;
|
|
42
|
-
setData(loadedData);
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
}, []);
|
|
46
|
-
|
|
47
|
-
return (
|
|
48
|
-
<div
|
|
49
|
-
class={"page" + (isCurrent ? ' current' : '')}
|
|
50
|
-
id={page.id === undefined ? undefined : 'page_' + page.id}
|
|
51
|
-
>
|
|
52
|
-
|
|
53
|
-
{/* Make request parameters and api data accessible from the page component */}
|
|
54
|
-
{page.component ? (
|
|
55
|
-
|
|
56
|
-
<page.component {...gui.request.data} {...data} />
|
|
57
|
-
|
|
58
|
-
) : null}
|
|
59
|
-
|
|
60
|
-
</div>
|
|
61
|
-
)
|
|
62
|
-
}
|
|
30
|
+
const LogPrefix = `[router][component]`
|
|
63
31
|
|
|
64
32
|
/*----------------------------------
|
|
65
33
|
- COMPONENT
|
|
66
34
|
----------------------------------*/
|
|
67
|
-
export default () => {
|
|
35
|
+
export default ({ service: router }: { service: Router }) => {
|
|
68
36
|
|
|
69
|
-
const
|
|
37
|
+
const context = useContext();
|
|
70
38
|
|
|
71
39
|
const [pages, setPages] = React.useState<{
|
|
72
|
-
current: undefined |
|
|
73
|
-
//previous: undefined |
|
|
40
|
+
current: undefined | Page,
|
|
41
|
+
//previous: undefined | Page
|
|
74
42
|
}>({
|
|
75
|
-
current:
|
|
43
|
+
current: context.page,
|
|
76
44
|
//previous: undefined
|
|
77
45
|
});
|
|
78
46
|
|
|
@@ -82,8 +50,8 @@ export default () => {
|
|
|
82
50
|
// If needed to play with pages, do it in the setPages callback below
|
|
83
51
|
|
|
84
52
|
// Load the route chunks
|
|
85
|
-
|
|
86
|
-
const newpage =
|
|
53
|
+
context.request = request;
|
|
54
|
+
const newpage = context.page = await router.resolve(request);
|
|
87
55
|
|
|
88
56
|
// Page not found: Directly load with the browser
|
|
89
57
|
if (newpage === undefined) {
|
|
@@ -94,22 +62,34 @@ export default () => {
|
|
|
94
62
|
return;
|
|
95
63
|
}
|
|
96
64
|
|
|
97
|
-
// Set
|
|
98
|
-
newpage.
|
|
65
|
+
// Set.loading state
|
|
66
|
+
newpage.isLoading = true;
|
|
67
|
+
newpage.loading = <i src="spin" />
|
|
99
68
|
// Add page container
|
|
100
69
|
setPages( pages => {
|
|
101
70
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
71
|
+
const currentRoute = pages.current?.route;
|
|
72
|
+
|
|
73
|
+
// Check if the page changed
|
|
74
|
+
if (currentRoute?.path === request.path) {
|
|
75
|
+
console.warn(LogPrefix, "Canceling navigation to the same page:", {...request});
|
|
105
76
|
return pages;
|
|
106
77
|
}
|
|
107
78
|
|
|
108
|
-
//
|
|
109
|
-
const curLayout =
|
|
110
|
-
const
|
|
111
|
-
if (
|
|
112
|
-
|
|
79
|
+
// If if the layout changed
|
|
80
|
+
const curLayout = currentRoute?.options.layout;
|
|
81
|
+
const newLayout = newpage?.route.options.layout;
|
|
82
|
+
if (newLayout && curLayout && newLayout.path !== curLayout.path) {
|
|
83
|
+
|
|
84
|
+
// TEMPORARY FIX: reload everything when we change layout
|
|
85
|
+
// Because layout can have a different theme
|
|
86
|
+
// But when we call setLayout, the style of the previous layout are still oaded and applied
|
|
87
|
+
// Find a way to unload the previous layout / page resources before to load the new one
|
|
88
|
+
console.log(LogPrefix, `Changing layout. Before:`, curLayout, 'New layout:', newLayout);
|
|
89
|
+
window.location.replace(request.path);
|
|
90
|
+
return pages;
|
|
91
|
+
|
|
92
|
+
context.app.setLayout(newLayout);
|
|
113
93
|
}
|
|
114
94
|
|
|
115
95
|
// Remove old page after the aff-page css transition
|
|
@@ -128,8 +108,8 @@ export default () => {
|
|
|
128
108
|
});
|
|
129
109
|
}
|
|
130
110
|
|
|
131
|
-
const restoreScroll = (currentPage?:
|
|
132
|
-
&& document.getElementById( currentPage.
|
|
111
|
+
const restoreScroll = (currentPage?: Page) => currentPage?.scrollToId
|
|
112
|
+
&& document.getElementById( currentPage.scrollToId.substring(1) )?.scrollIntoView({
|
|
133
113
|
behavior: "smooth",
|
|
134
114
|
block: "start",
|
|
135
115
|
inline: "nearest"
|
|
@@ -138,19 +118,15 @@ export default () => {
|
|
|
138
118
|
// First load
|
|
139
119
|
React.useEffect(() => {
|
|
140
120
|
|
|
141
|
-
/*gui.api.set = (newData: TObjetDonnees) => {
|
|
142
|
-
setPage(a => ({ ...a, data: { ...a.data, ...newData } }));
|
|
143
|
-
}*/
|
|
144
|
-
|
|
145
121
|
// Resolve page if it wasn't done via SSR
|
|
146
|
-
if (
|
|
147
|
-
resolvePage(
|
|
122
|
+
if (context.page === undefined)
|
|
123
|
+
resolvePage(context.request);
|
|
148
124
|
|
|
149
125
|
// Foreach URL change (Ex: bowser' back buttton)
|
|
150
126
|
return history?.listen(async (locationUpdate) => {
|
|
151
127
|
|
|
152
128
|
// Load the concerned route
|
|
153
|
-
const request = new ClientRequest(locationUpdate.location,
|
|
129
|
+
const request = new ClientRequest(locationUpdate.location, context);
|
|
154
130
|
await resolvePage(request);
|
|
155
131
|
|
|
156
132
|
// Scroll to the selected content via url hash
|
|
@@ -161,16 +137,15 @@ export default () => {
|
|
|
161
137
|
// On every page change
|
|
162
138
|
React.useEffect(() => {
|
|
163
139
|
|
|
164
|
-
// Tracking
|
|
165
|
-
gui.event('pageview');
|
|
166
140
|
// Reset scroll
|
|
167
141
|
window.scrollTo(0, 0);
|
|
168
142
|
// Should be called AFTER rendering the page (so after the state change)
|
|
169
|
-
|
|
170
|
-
|
|
143
|
+
pages.current?.updateClient();
|
|
171
144
|
// Scroll to the selected content via url hash
|
|
172
145
|
restoreScroll(pages.current);
|
|
173
|
-
|
|
146
|
+
|
|
147
|
+
// Hooks
|
|
148
|
+
router.runHook('page.changed', pages.current)
|
|
174
149
|
|
|
175
150
|
}, [pages.current]);
|
|
176
151
|
|
|
@@ -181,7 +156,10 @@ export default () => {
|
|
|
181
156
|
)*/}
|
|
182
157
|
|
|
183
158
|
{pages.current && (
|
|
184
|
-
<
|
|
159
|
+
<PageComponent page={pages.current}
|
|
160
|
+
isCurrent
|
|
161
|
+
key={pages.current.id === undefined ? undefined : 'page_' + pages.current.id}
|
|
162
|
+
/>
|
|
185
163
|
)}
|
|
186
164
|
</>
|
|
187
165
|
}
|
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
/*----------------------------------
|
|
2
|
+
- DEPENDANCES
|
|
3
|
+
----------------------------------*/
|
|
4
|
+
|
|
5
|
+
// Npm
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import ReactDOM from 'react-dom';
|
|
8
|
+
|
|
9
|
+
// Core
|
|
10
|
+
import type {
|
|
11
|
+
default as ServerRouter,
|
|
12
|
+
Request as ServerRequest,
|
|
13
|
+
Response as ServerResponse
|
|
14
|
+
} from '@server/services/router';
|
|
15
|
+
import type { TBasicSSrData } from '@server/services/router/response';
|
|
16
|
+
|
|
17
|
+
import { Erreur } from '@common/errors';
|
|
18
|
+
import BaseRouter, {
|
|
19
|
+
defaultOptions, TRoute, TErrorRoute, TClientOrServerContext, TRouteModule
|
|
20
|
+
} from '@common/router'
|
|
21
|
+
import { getRegisterPageArgs, buildRegex } from '@common/router/register';
|
|
22
|
+
import { TFetcherList } from '@common/router/request/api';
|
|
23
|
+
import type { TFrontRenderer, TDataProvider } from '@common/router/response/page';
|
|
24
|
+
|
|
25
|
+
import App from '@client/app/component';
|
|
26
|
+
import type ClientApplication from '@client/app';
|
|
27
|
+
import Service from '@client/app/service';
|
|
28
|
+
|
|
29
|
+
// Specific
|
|
30
|
+
import ClientRequest from './request';
|
|
31
|
+
import { location, history } from './request/history';
|
|
32
|
+
import ClientResponse from './response';
|
|
33
|
+
import ClientPage from './response/page';
|
|
34
|
+
|
|
35
|
+
// Routes (import __register)
|
|
36
|
+
import * as coreRoutes from '@client/pages/**/*.tsx';
|
|
37
|
+
import * as appRoutes from '@/client/pages/**/*.tsx';
|
|
38
|
+
|
|
39
|
+
/*----------------------------------
|
|
40
|
+
- CONFIG
|
|
41
|
+
----------------------------------*/
|
|
42
|
+
|
|
43
|
+
const debug = true;
|
|
44
|
+
const LogPrefix = '[router]'
|
|
45
|
+
|
|
46
|
+
/*----------------------------------
|
|
47
|
+
- TYPES
|
|
48
|
+
----------------------------------*/
|
|
49
|
+
|
|
50
|
+
// Client router can handle Client requests AND Server requests (for pages only)
|
|
51
|
+
export type { default as ClientResponse, TRouterContext } from "./response";
|
|
52
|
+
export { Link } from './components/Link';
|
|
53
|
+
|
|
54
|
+
export type Router = ClientRouter | ServerRouter;
|
|
55
|
+
|
|
56
|
+
export type Request = ClientRequest<ClientRouter> | ServerRequest<ServerRouter>;
|
|
57
|
+
|
|
58
|
+
export type Response = ClientResponse<ClientRouter> | ServerResponse<ServerRouter>;
|
|
59
|
+
|
|
60
|
+
/*----------------------------------
|
|
61
|
+
- TYPES: ROUTES LOADING
|
|
62
|
+
----------------------------------*/
|
|
63
|
+
|
|
64
|
+
export type TRegisterPageArgs<TProvidedData extends TFetcherList = {}, TRouter extends Router = Router> = ([
|
|
65
|
+
path: string,
|
|
66
|
+
controller: TDataProvider<TProvidedData> | null,
|
|
67
|
+
renderer: TFrontRenderer<TProvidedData>
|
|
68
|
+
] | [
|
|
69
|
+
path: string,
|
|
70
|
+
options: Partial<TRoute["options"]>,
|
|
71
|
+
controller: TDataProvider<TProvidedData> | null,
|
|
72
|
+
renderer: TFrontRenderer<TProvidedData>
|
|
73
|
+
])
|
|
74
|
+
|
|
75
|
+
// Route definition passed by the server
|
|
76
|
+
export type TSsrUnresolvedRoute = {
|
|
77
|
+
chunk: string,
|
|
78
|
+
} & ({
|
|
79
|
+
// Normal route
|
|
80
|
+
regex: string,
|
|
81
|
+
keys: TRoute["keys"]
|
|
82
|
+
} | {
|
|
83
|
+
// Error
|
|
84
|
+
code: number
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
// Route definition without having loaded the controller
|
|
88
|
+
type TUnresolvedRoute = TUnresolvedErrorRoute | TUnresolvedNormalRoute;
|
|
89
|
+
|
|
90
|
+
export type TUnresolvedErrorRoute = {
|
|
91
|
+
index: number,
|
|
92
|
+
chunk: string,
|
|
93
|
+
code: number,
|
|
94
|
+
load: TRouteLoader<TErrorRoute>,
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export type TUnresolvedNormalRoute = {
|
|
98
|
+
index: number,
|
|
99
|
+
chunk: string,
|
|
100
|
+
code: number,
|
|
101
|
+
load: TRouteLoader<TErrorRoute>,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
type TRouteLoader<Route extends TRoute | TErrorRoute = TRoute | TErrorRoute> = () => Promise<TRouteModule<Route>>;
|
|
105
|
+
|
|
106
|
+
export type TFetchedRoute = Pick<TRoute, 'path' | 'options' | 'controller' | 'method'>
|
|
107
|
+
|
|
108
|
+
export type TRoutesLoaders = {
|
|
109
|
+
[chunkId: string]: () => Promise</* Preloaded via require() */TFetchedRoute | /* Loader via import() */TRouteLoader/* | undefined*/>
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/*----------------------------------
|
|
113
|
+
- SERVICE TYPES
|
|
114
|
+
----------------------------------*/
|
|
115
|
+
|
|
116
|
+
export type THookCallback<TRouter extends ClientRouter> = (request: ClientRequest<TRouter>) => void;
|
|
117
|
+
|
|
118
|
+
type THookName = 'location.change' | 'page.changed'
|
|
119
|
+
|
|
120
|
+
type Config = {
|
|
121
|
+
preload: string[], // List of globs
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/*----------------------------------
|
|
125
|
+
- ROUTER
|
|
126
|
+
----------------------------------*/
|
|
127
|
+
export default class ClientRouter<
|
|
128
|
+
TApplication extends ClientApplication = ClientApplication
|
|
129
|
+
> extends Service<Config, ClientApplication> implements BaseRouter {
|
|
130
|
+
|
|
131
|
+
public ssrData = window["ssr"] as (TBasicSSrData | undefined);
|
|
132
|
+
public ssrRoutes = window["routes"] as TSsrUnresolvedRoute[];
|
|
133
|
+
|
|
134
|
+
public constructor(app: TApplication, config: Config) {
|
|
135
|
+
|
|
136
|
+
super(app, config);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
public async start() {
|
|
140
|
+
|
|
141
|
+
const currentRoute = await this.registerRoutes();
|
|
142
|
+
|
|
143
|
+
this.initialRender(currentRoute);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
public go( url: string ) {
|
|
147
|
+
history?.replace(url);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/*----------------------------------
|
|
151
|
+
- REGISTRATION
|
|
152
|
+
----------------------------------*/
|
|
153
|
+
|
|
154
|
+
public routes: (TRoute | TUnresolvedNormalRoute)[] = [];
|
|
155
|
+
public errors: { [code: number]: TErrorRoute | TUnresolvedErrorRoute } = {};
|
|
156
|
+
|
|
157
|
+
public async registerRoutes() {
|
|
158
|
+
|
|
159
|
+
const loaders: TRoutesLoaders = { ...coreRoutes, ...appRoutes }
|
|
160
|
+
let currentRoute: TUnresolvedRoute | undefined;
|
|
161
|
+
debug && console.log(LogPrefix, `Indexing routes and finding the current route from ssr data:`, this.ssrData);
|
|
162
|
+
|
|
163
|
+
// Associe la liste des routes (obtenue via ssr) à leur loader
|
|
164
|
+
for (let routeIndex = 0; routeIndex < this.ssrRoutes.length; routeIndex++) {
|
|
165
|
+
|
|
166
|
+
const ssrRoute = this.ssrRoutes[routeIndex];
|
|
167
|
+
|
|
168
|
+
if (loaders[ssrRoute.chunk] === undefined) {
|
|
169
|
+
console.error("Chunk id not found for ssr route:", ssrRoute, "Searched in:", loaders);
|
|
170
|
+
throw new Error(`Loader not found for chunk id ${ssrRoute.chunk}`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// TODO: Fix types
|
|
174
|
+
const loader = loaders[ssrRoute.chunk];
|
|
175
|
+
|
|
176
|
+
// Register the route
|
|
177
|
+
let route: TUnresolvedRoute;
|
|
178
|
+
if ('code' in ssrRoute)
|
|
179
|
+
route = this.errors[ssrRoute.code] = {
|
|
180
|
+
code: ssrRoute.code,
|
|
181
|
+
chunk: ssrRoute.chunk,
|
|
182
|
+
load: loader,
|
|
183
|
+
}
|
|
184
|
+
else
|
|
185
|
+
route = this.routes[routeIndex] = {
|
|
186
|
+
index: routeIndex,
|
|
187
|
+
chunk: ssrRoute.chunk,
|
|
188
|
+
regex: new RegExp(ssrRoute.regex),
|
|
189
|
+
keys: ssrRoute.keys,
|
|
190
|
+
load: loader,
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
debug && console.log(LogPrefix, `${route.chunk}`, route);
|
|
194
|
+
|
|
195
|
+
// Detect if it's the current route
|
|
196
|
+
if (currentRoute === undefined) {
|
|
197
|
+
|
|
198
|
+
const isCurrentRoute = (
|
|
199
|
+
this.ssrData !== undefined
|
|
200
|
+
&&
|
|
201
|
+
route.chunk === this.ssrData.page.chunkId
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
if (isCurrentRoute) {
|
|
205
|
+
currentRoute = route;
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return currentRoute;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
public page(...args: TRegisterPageArgs): TRoute {
|
|
215
|
+
|
|
216
|
+
const { path, options, controller, renderer } = getRegisterPageArgs(...args);
|
|
217
|
+
|
|
218
|
+
// S'il s'agit d'une page, son id doit avoir été injecté via le plugin babel
|
|
219
|
+
const id = options["id"];
|
|
220
|
+
if (id === undefined)
|
|
221
|
+
throw new Error(`ID had not been injected into page options via the routes babel plugin for route ${path}.`);
|
|
222
|
+
|
|
223
|
+
const { regex, keys } = buildRegex(path);
|
|
224
|
+
|
|
225
|
+
const route: TRoute = {
|
|
226
|
+
method: 'GET',
|
|
227
|
+
path,
|
|
228
|
+
regex,
|
|
229
|
+
keys,
|
|
230
|
+
options: {
|
|
231
|
+
...defaultOptions,
|
|
232
|
+
...options
|
|
233
|
+
},
|
|
234
|
+
controller: (context: TClientOrServerContext) => new ClientPage(controller, renderer, context)
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
this.routes.push(route);
|
|
238
|
+
|
|
239
|
+
return route;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
public error(code: number, options: TRoute["options"], renderer: TFrontRenderer<{}, { message: string }>) {
|
|
243
|
+
|
|
244
|
+
const route: TErrorRoute = {
|
|
245
|
+
code,
|
|
246
|
+
controller: (context: TClientOrServerContext) => new ClientPage(null, renderer, context),
|
|
247
|
+
options
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
this.errors[code] = route;
|
|
251
|
+
|
|
252
|
+
return route;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
/*----------------------------------
|
|
257
|
+
- RESOLUTION
|
|
258
|
+
----------------------------------*/
|
|
259
|
+
public async resolve(request: ClientRequest<this>): Promise<ClientPage | undefined | null> {
|
|
260
|
+
|
|
261
|
+
debug && console.log(LogPrefix, 'Resolving request', request.path, Object.keys(request.data));
|
|
262
|
+
this.runHook('location.change', request);
|
|
263
|
+
|
|
264
|
+
for (let iRoute = 0; iRoute < this.routes.length; iRoute++) {
|
|
265
|
+
|
|
266
|
+
let route = this.routes[iRoute];
|
|
267
|
+
if (!('regex' in route))
|
|
268
|
+
continue;
|
|
269
|
+
|
|
270
|
+
const match = route.regex.exec(request.path);
|
|
271
|
+
if (!match)
|
|
272
|
+
continue;
|
|
273
|
+
|
|
274
|
+
// URL data
|
|
275
|
+
for (let iKey = 0; iKey < route.keys.length; iKey++) {
|
|
276
|
+
const nomParam = route.keys[iKey];
|
|
277
|
+
if (typeof nomParam === 'string') // number = sans nom
|
|
278
|
+
request.data[nomParam] = match[iKey + 1]
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Create response
|
|
282
|
+
debug && console.log(LogPrefix, 'Resolved request', request.path, '| Route:', route);
|
|
283
|
+
const page = await this.createResponse(route, request);
|
|
284
|
+
|
|
285
|
+
return page;
|
|
286
|
+
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
return undefined;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
private async load(route: TUnresolvedNormalRoute): Promise<TRoute>;
|
|
293
|
+
private async load(route: TUnresolvedErrorRoute): Promise<TErrorRoute>;
|
|
294
|
+
private async load(route: TUnresolvedNormalRoute | TUnresolvedErrorRoute): Promise<TRoute | TErrorRoute> {
|
|
295
|
+
|
|
296
|
+
//throw new Error(`Failed to load route: ${route.chunk}`);
|
|
297
|
+
|
|
298
|
+
let fetched: TFetchedRoute;
|
|
299
|
+
if (typeof route.load === 'function') {
|
|
300
|
+
|
|
301
|
+
debug && console.log(`Fetching route ${route.chunk} ...`, route);
|
|
302
|
+
try {
|
|
303
|
+
|
|
304
|
+
const loaded = await route.load();
|
|
305
|
+
|
|
306
|
+
fetched = loaded.__register(this.app);
|
|
307
|
+
|
|
308
|
+
} catch (e) {
|
|
309
|
+
console.error(`Failed to fetch the route ${route.chunk}`, e);
|
|
310
|
+
this.app.handleError(new Error("Failed to load content. Please make sure you're connected to Internet."));
|
|
311
|
+
throw e;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
} else {
|
|
315
|
+
|
|
316
|
+
debug && console.log(`Route already fetched: ${route.chunk}`, route.load);
|
|
317
|
+
fetched = route.load;
|
|
318
|
+
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
debug && console.log(`Route fetched: ${route.chunk}`, fetched);
|
|
322
|
+
return {
|
|
323
|
+
...fetched,
|
|
324
|
+
regex: route.regex,
|
|
325
|
+
keys: route.keys
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
public set(data: TObjetDonnees) {
|
|
330
|
+
throw new Error(`router.set was not attached to the router component.`);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
private async initialRender(route: TUnresolvedRoute | undefined) {
|
|
334
|
+
|
|
335
|
+
debug && console.log(LogPrefix, `Initial render route`, route);
|
|
336
|
+
|
|
337
|
+
if (!location)
|
|
338
|
+
throw new Error(`Unable to retrieve current location.`);
|
|
339
|
+
|
|
340
|
+
if (!route)
|
|
341
|
+
throw new Error(`Unable to resolve route.`);
|
|
342
|
+
|
|
343
|
+
const request = new ClientRequest(location, this);
|
|
344
|
+
|
|
345
|
+
// Restituate SSR response
|
|
346
|
+
let apiData: {} = {}
|
|
347
|
+
if (this.ssrData) {
|
|
348
|
+
|
|
349
|
+
console.log("SSR Response restitution ...");
|
|
350
|
+
|
|
351
|
+
request.user = this.ssrData.user || null;
|
|
352
|
+
|
|
353
|
+
request.data = this.ssrData.request.data;
|
|
354
|
+
|
|
355
|
+
apiData = this.ssrData.page.data || {};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Replacer api data par ssr data
|
|
359
|
+
|
|
360
|
+
const response = await this.createResponse(route, request, apiData)
|
|
361
|
+
|
|
362
|
+
ReactDOM.hydrate( <App context={response.context} />, document.body, () => {
|
|
363
|
+
|
|
364
|
+
console.log(`Render complete`);
|
|
365
|
+
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
private async createResponse(
|
|
370
|
+
route: TUnresolvedRoute | TRoute,
|
|
371
|
+
request: ClientRequest<this>,
|
|
372
|
+
pageData: {} = {}
|
|
373
|
+
): Promise<ClientPage> {
|
|
374
|
+
|
|
375
|
+
// Load if not done before
|
|
376
|
+
if ('load' in route)
|
|
377
|
+
route = this.routes[route.index] = await this.load(route);
|
|
378
|
+
|
|
379
|
+
// Run controller
|
|
380
|
+
// TODO: tell that ruController on the client side always returns pages
|
|
381
|
+
try {
|
|
382
|
+
|
|
383
|
+
const response = new ClientResponse<this, ClientPage>(request, route);
|
|
384
|
+
return await response.runController(pageData);
|
|
385
|
+
|
|
386
|
+
} catch (error) {
|
|
387
|
+
|
|
388
|
+
return await this.createErrorResponse(error, request);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
private async createErrorResponse(
|
|
393
|
+
e: any,
|
|
394
|
+
request: ClientRequest<this>,
|
|
395
|
+
pageData: {} = {}
|
|
396
|
+
): Promise<ClientPage> {
|
|
397
|
+
|
|
398
|
+
const code = 'http' in e ? e.http : 500;
|
|
399
|
+
console.log(`Loading error page ` + code);
|
|
400
|
+
let route = this.errors[code];
|
|
401
|
+
|
|
402
|
+
// Nor page configurated for this error
|
|
403
|
+
if (route === undefined) {
|
|
404
|
+
console.error(`Error page for http error code ${code} not found.`, this.errors, this.routes);
|
|
405
|
+
this.app.handleError(e, 404);
|
|
406
|
+
throw new Error(`Error page for http error code ${code} not found.`);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Load if not done before
|
|
410
|
+
if ('load' in route)
|
|
411
|
+
route = this.errors[code] = await this.load(route);
|
|
412
|
+
|
|
413
|
+
const response = new ClientResponse<this, ClientPage>(request, route);
|
|
414
|
+
return await response.runController(pageData);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/*----------------------------------
|
|
418
|
+
- HOOKS
|
|
419
|
+
----------------------------------*/
|
|
420
|
+
private hooks: {
|
|
421
|
+
[hookname in THookName]?: (THookCallback<this> | null)[]
|
|
422
|
+
} = {}
|
|
423
|
+
|
|
424
|
+
public on(hookName: THookName, callback: THookCallback<this>) {
|
|
425
|
+
|
|
426
|
+
debug && console.info(LogPrefix, `Register hook ${hookName}`);
|
|
427
|
+
|
|
428
|
+
let cbIndex: number;
|
|
429
|
+
let callbacks = this.hooks[hookName];
|
|
430
|
+
if (!callbacks) {
|
|
431
|
+
cbIndex = 0;
|
|
432
|
+
callbacks = this.hooks[hookName] = [callback]
|
|
433
|
+
} else {
|
|
434
|
+
cbIndex = callbacks.length;
|
|
435
|
+
callbacks.push(callback);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Listener remover
|
|
439
|
+
return () => {
|
|
440
|
+
debug && console.info(LogPrefix, `De-register hook ${hookName} (index ${cbIndex})`);
|
|
441
|
+
delete (callbacks as THookCallback<this>[])[cbIndex];
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
public runHook(hookName: THookName, request: ClientRequest<this>) {
|
|
447
|
+
const callbacks = this.hooks[hookName];
|
|
448
|
+
if (callbacks)
|
|
449
|
+
for (const callback of callbacks)
|
|
450
|
+
// callback can be null since we use delete to unregister
|
|
451
|
+
callback && callback(request);
|
|
452
|
+
}
|
|
453
|
+
}
|