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.
Files changed (148) hide show
  1. package/changelog.md +5 -0
  2. package/doc/TODO.md +71 -0
  3. package/package.json +5 -4
  4. package/src/client/{App.tsx → app/component.tsx} +15 -11
  5. package/src/client/app/index.ts +128 -0
  6. package/src/client/app/service.ts +34 -0
  7. package/src/client/app.tsconfig.json +0 -4
  8. package/src/client/assets/css/components.less +52 -0
  9. package/src/client/assets/css/core.less +7 -28
  10. package/src/client/assets/css/theme.less +1 -1
  11. package/src/client/assets/css/{borders.less → utils/borders.less} +0 -0
  12. package/src/client/assets/css/{layouts.less → utils/layouts.less} +0 -0
  13. package/src/client/assets/css/{medias.less → utils/medias.less} +14 -1
  14. package/src/client/assets/css/{sizing.less → utils/sizing.less} +0 -0
  15. package/src/client/assets/css/{spacing.less → utils/spacing.less} +0 -0
  16. package/src/client/components/Card/index.tsx +13 -7
  17. package/src/client/components/Dialog/Manager.tsx +41 -14
  18. package/src/client/components/Dialog/index.less +2 -4
  19. package/src/client/components/Form/index.tsx +1 -1
  20. package/src/client/components/Row/index.less +0 -2
  21. package/src/client/components/Table/index.tsx +3 -2
  22. package/src/client/components/button.tsx +2 -2
  23. package/src/client/components/containers/Popover/index.tsx +1 -1
  24. package/src/client/components/containers/champs.less +0 -2
  25. package/src/client/components/data/spintext/index.tsx +1 -1
  26. package/src/client/components/dropdown/index.tsx +1 -1
  27. package/src/client/components/index.ts +23 -0
  28. package/src/client/components/input/BaseV2/index.less +0 -2
  29. package/src/client/components/input/BaseV2/index.tsx +1 -1
  30. package/src/client/components/input/Date/index.less +0 -2
  31. package/src/client/components/input/Periode/index.less +0 -2
  32. package/src/client/components/input/Radio/index.less +0 -2
  33. package/src/client/components/input/UploadImage/index.less +0 -2
  34. package/src/client/components/input/UploadImage/index.tsx +1 -1
  35. package/src/client/hooks/index.ts +5 -0
  36. package/src/client/hooks/useState/index.tsx +2 -2
  37. package/src/client/hooks.ts +22 -0
  38. package/src/client/index.ts +5 -0
  39. package/src/client/pages/_layout/landing/index.tsx +0 -2
  40. package/src/client/pages/_messages/400.tsx +2 -2
  41. package/src/client/pages/_messages/401.tsx +2 -2
  42. package/src/client/pages/_messages/403.tsx +2 -2
  43. package/src/client/pages/_messages/404.tsx +2 -2
  44. package/src/client/pages/_messages/500.tsx +2 -2
  45. package/src/client/pages/bug.tsx +1 -1
  46. package/src/client/pages/useHeader.tsx +1 -1
  47. package/src/client/{context/captcha.ts → services/captcha/index.ts} +0 -0
  48. package/src/client/services/metrics/index.ts +37 -0
  49. package/src/client/{router → services/router/components}/Link.tsx +1 -1
  50. package/src/client/services/router/components/Page.tsx +59 -0
  51. package/src/client/{router/component.tsx → services/router/components/router.tsx} +52 -74
  52. package/src/client/services/router/index.tsx +453 -0
  53. package/src/client/services/router/request/api.ts +227 -0
  54. package/src/client/{router → services/router}/request/history.ts +0 -0
  55. package/src/client/services/router/request/index.ts +52 -0
  56. package/src/client/services/router/response/index.tsx +107 -0
  57. package/src/client/services/router/response/page.ts +90 -0
  58. package/src/client/{context/socket.ts → services/socket/index.ts} +2 -2
  59. package/src/client/utils/dom.ts +1 -1
  60. package/src/common/app/index.ts +9 -0
  61. package/src/common/data/chaines/index.ts +9 -6
  62. package/src/common/data/input/validate.ts +3 -166
  63. package/src/common/data/objets.ts +25 -0
  64. package/src/common/data/tableaux.ts +8 -0
  65. package/src/common/errors/index.ts +3 -1
  66. package/src/common/router/index.ts +67 -88
  67. package/src/common/router/layouts.ts +50 -0
  68. package/src/common/router/register.ts +62 -0
  69. package/src/common/router/request/api.ts +72 -0
  70. package/src/common/router/request/index.ts +31 -0
  71. package/src/common/router/{response.ts → response/index.ts} +9 -13
  72. package/src/common/router/response/page.ts +46 -54
  73. package/src/common/validation/index.ts +3 -0
  74. package/src/common/validation/schema.ts +185 -0
  75. package/src/common/validation/validator.ts +95 -0
  76. package/src/common/validation/validators.ts +313 -0
  77. package/src/server/app/config.ts +9 -27
  78. package/src/server/app/index.ts +81 -124
  79. package/src/server/app/service.ts +98 -0
  80. package/src/server/app.tsconfig.json +0 -8
  81. package/src/server/index.ts +5 -0
  82. package/src/server/patch.ts +0 -6
  83. package/src/server/{data/Cache.ts → services/cache/index.ts} +79 -47
  84. package/src/server/services/console/bugReporter.ts +26 -16
  85. package/src/server/services/console/index.ts +59 -51
  86. package/src/server/services/cron/index.ts +12 -26
  87. package/src/server/services/database/bucket.ts +40 -0
  88. package/src/server/services/database/connection.ts +213 -80
  89. package/src/server/services/database/datatypes.ts +63 -40
  90. package/src/server/services/database/debug.ts +20 -0
  91. package/src/server/services/database/index.ts +295 -272
  92. package/src/server/services/database/metas.ts +246 -135
  93. package/src/server/services/database/stats.ts +151 -126
  94. package/src/server/services/email/index.ts +30 -62
  95. package/src/server/services/email/transporter.ts +38 -0
  96. package/src/server/services/{router/request/services → metrics}/detect.ts +8 -10
  97. package/src/server/services/{router/request/services/tracking.ts → metrics/index.ts} +68 -45
  98. package/src/server/services/{http → router/http}/index.ts +28 -70
  99. package/src/server/services/{http → router/http}/multipart.ts +0 -0
  100. package/src/server/services/{http → router/http}/session.ts.old +0 -0
  101. package/src/server/services/router/index.ts +273 -202
  102. package/src/server/services/router/request/api.ts +76 -0
  103. package/src/server/services/router/request/index.ts +16 -97
  104. package/src/server/services/router/request/service.ts +21 -0
  105. package/src/server/services/router/response/index.ts +131 -65
  106. package/src/server/services/router/response/{filter → mask}/Filter.ts +0 -0
  107. package/src/server/services/router/response/{filter → mask}/index.ts +0 -2
  108. package/src/server/services/router/response/{filter → mask}/selecteurs.ts +0 -0
  109. package/src/server/services/router/response/page/document.tsx +194 -0
  110. package/src/server/services/router/response/page/index.tsx +157 -0
  111. package/src/server/{libs/pages → services/router/response/page}/schemaGenerator.ts +0 -0
  112. package/src/server/services/router/service.ts +48 -0
  113. package/src/server/services/schema/index.ts +47 -0
  114. package/src/server/services/schema/request.ts +55 -0
  115. package/src/server/services/schema/router.ts +33 -0
  116. package/src/server/services/socket/index.ts +38 -43
  117. package/src/server/services/socket/scope.ts +6 -4
  118. package/src/server/services/users/index.ts +203 -0
  119. package/src/server/services/{auth/base.ts → users/old.ts} +28 -112
  120. package/src/server/services/users/router/index.ts +72 -0
  121. package/src/server/services/users/router/request.ts +49 -0
  122. package/src/server/{data → services_old}/SocketClient.ts +0 -0
  123. package/src/server/{data/Token.olg.ts → services_old/Token.old.ts} +0 -0
  124. package/src/server/{data → services_old}/aes.ts +0 -0
  125. package/src/types/aliases.d.ts +43 -2
  126. package/templates/composant.tsx +1 -1
  127. package/templates/modal.tsx +1 -1
  128. package/templates/page.tsx +1 -1
  129. package/tsconfig.common.json +0 -4
  130. package/src/client/assets/css/components/components.less +0 -31
  131. package/src/client/context/api.ts +0 -92
  132. package/src/client/context/index.ts +0 -246
  133. package/src/client/index.tsx +0 -129
  134. package/src/client/router/index.ts +0 -286
  135. package/src/client/router/request/index.ts +0 -106
  136. package/src/client/router/response/index.ts +0 -38
  137. package/src/client/router/route.ts +0 -75
  138. package/src/common/data/input/validators/basic.ts +0 -299
  139. package/src/common/data/input/validators/build.ts +0 -63
  140. package/src/common/router/request.ts +0 -83
  141. package/src/server/data/ApiClient.ts +0 -119
  142. package/src/server/data/input.ts +0 -41
  143. package/src/server/libs/pages/document.static.tsx +0 -41
  144. package/src/server/libs/pages/document.tsx +0 -203
  145. package/src/server/libs/pages/render.tsx +0 -90
  146. package/src/server/routes/auth.ts +0 -151
  147. package/src/server/services/redis/index.ts +0 -71
  148. 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
- // Libs
8
- import useContext from '@client/context';
9
- import ClientRequest from './request';
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 Page = ({ page, isCurrent }: { page: PageResponse, isCurrent?: boolean }) => {
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 gui = useContext();
37
+ const context = useContext();
70
38
 
71
39
  const [pages, setPages] = React.useState<{
72
- current: undefined | PageResponse,
73
- //previous: undefined | PageResponse
40
+ current: undefined | Page,
41
+ //previous: undefined | Page
74
42
  }>({
75
- current: gui.page,
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
- gui.request = request;
86
- const newpage = gui.page = await router.resolve(request, gui);
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 loading state
98
- newpage.loading = <i src="spin" />
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
- // Page unchanges
103
- if (pages.current?.route.path === request.path) {
104
- console.warn("Canceling navigation to the same page:", {...request});
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
- // Layout change
109
- const curLayout = pages.current?.route.options.layout;
110
- const newlayout = newpage?.route.options.layout;
111
- if (newlayout && curLayout && newlayout.path !== curLayout.path) {
112
- gui.setLayout(newlayout);
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?: PageResponse) => currentPage?.hash
132
- && document.getElementById( currentPage.hash.substring(1) )?.scrollIntoView({
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 (gui.page === undefined)
147
- resolvePage(gui.request);
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, gui);
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
- gui.page?.updateClient();
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
- <Page page={pages.current} isCurrent key={pages.current.id === undefined ? undefined : 'page_' + pages.current.id} />
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
+ }