5htp-core 0.4.4 → 0.4.5

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "5htp-core",
3
3
  "description": "Convenient TypeScript framework designed for Performance and Productivity.",
4
- "version": "0.4.4",
4
+ "version": "0.4.5",
5
5
  "author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
6
6
  "repository": "git://github.com/gaetanlegac/5htp-core.git",
7
7
  "license": "MIT",
@@ -35,7 +35,6 @@
35
35
  // Hover
36
36
  //transition: all .5s linear;
37
37
  &:hover,
38
- &.active,
39
38
  li:hover > & {
40
39
 
41
40
  color: var(--cTxtImportant);
@@ -46,6 +45,38 @@
46
45
  }
47
46
  }
48
47
 
48
+ &.active {
49
+ &::after {
50
+ content: ' ';
51
+ display: block;
52
+ position: absolute;
53
+
54
+ background: @c1;
55
+ height: @sizeActiveIndicator;
56
+ width: @sizeActiveIndicator;
57
+ border-radius: 50%;
58
+
59
+ // Default: bottom
60
+ left: 50%;
61
+ margin-left: -@sizeActiveIndicator / 2;
62
+ bottom: -@sizeActiveIndicator / 2;
63
+ }
64
+
65
+ .col > &::after,
66
+ .col > li > &::after {
67
+
68
+ // Reset potition
69
+ left: auto;
70
+ margin-left: auto;
71
+ bottom: auto;
72
+
73
+ // Position right
74
+ top: 50%;
75
+ margin-top: -@sizeActiveIndicator / 2;
76
+ right: -@sizeActiveIndicator / 2;
77
+ }
78
+ }
79
+
49
80
  // Click
50
81
  &.pressed {
51
82
  transform: scale(0.9);
@@ -129,29 +160,6 @@
129
160
  &.col {
130
161
  box-shadow: 0 0 0 0.2em @c2;
131
162
  }
132
-
133
- .menu &::after {
134
- content: ' ';
135
- display: block;
136
- position: absolute;
137
-
138
- background: @c1;
139
- height: @sizeActiveIndicator;
140
- width: @sizeActiveIndicator;
141
- border-radius: 50%;
142
- }
143
-
144
- .menu.row &::after {
145
- left: 50%;
146
- margin-left: -@sizeActiveIndicator / 2;
147
- bottom: -@sizeActiveIndicator / 2;
148
- }
149
-
150
- .menu.col &::after {
151
- top: 50%;
152
- margin-top: -@sizeActiveIndicator / 2;
153
- right: -@sizeActiveIndicator / 2;
154
- }
155
163
  }
156
164
 
157
165
  &[disabled] {
@@ -150,11 +150,6 @@ pre {
150
150
 
151
151
  --cTxtBase: #555;
152
152
 
153
- .card > & {
154
-
155
- padding: 3vh 0 !important;
156
- }
157
-
158
153
  display: flex;
159
154
  flex-direction: column;
160
155
  gap: 1em;
@@ -74,7 +74,7 @@ export default ({
74
74
  if (refCommit.current !== null)
75
75
  clearTimeout(refCommit.current);
76
76
 
77
- refCommit.current = setTimeout(commitValue, 500);
77
+ refCommit.current = setTimeout(commitValue, 100);
78
78
 
79
79
  }, [value]);
80
80
 
@@ -23,25 +23,34 @@ export type PropsPage<TParams extends { [cle: string]: unknown }> = TParams & {
23
23
  data: {[cle: string]: unknown}
24
24
  }
25
25
 
26
+ export type TProps = {
27
+ service?: ClientRouter,
28
+ loaderComponent?: React.ComponentType<{ isLoading: boolean }>,
29
+ }
30
+
26
31
  /*----------------------------------
27
32
  - PAGE STATE
28
33
  ----------------------------------*/
29
34
 
30
35
  const LogPrefix = `[router][component]`
31
36
 
32
- const PageLoading = ({ clientRouter }: { clientRouter?: ClientRouter }) => {
37
+ const PageLoading = ({ clientRouter, loaderComponent: LoaderComponent }: {
38
+ clientRouter?: ClientRouter,
39
+ loaderComponent?: React.ComponentType<{ isLoading: boolean }>,
40
+ }) => {
33
41
 
34
42
  const [isLoading, setLoading] = React.useState(false);
35
43
 
36
44
  if (clientRouter)
37
45
  clientRouter.setLoading = setLoading;
38
46
 
39
- return (
40
- <div id="loading" class={isLoading ? 'display' : ''}>
41
- <i src="spin" />
42
- </div>
43
- )
44
-
47
+ return LoaderComponent
48
+ ? <LoaderComponent isLoading={isLoading} />
49
+ : (
50
+ <div id="loading" class={isLoading ? 'display' : ''}>
51
+ <i src="spin" />
52
+ </div>
53
+ )
45
54
  }
46
55
 
47
56
  const scrollToElement = (selector: string) => document.querySelector( selector )
@@ -54,7 +63,7 @@ const scrollToElement = (selector: string) => document.querySelector( selector )
54
63
  /*----------------------------------
55
64
  - COMPONENT
56
65
  ----------------------------------*/
57
- export default ({ service: clientRouter }: { service?: ClientRouter }) => {
66
+ export default ({ service: clientRouter, loaderComponent }: TProps) => {
58
67
 
59
68
  /*----------------------------------
60
69
  - INIT
@@ -62,20 +71,18 @@ export default ({ service: clientRouter }: { service?: ClientRouter }) => {
62
71
 
63
72
  const context = useContext();
64
73
 
74
+ const [currentPage, setCurrentPage] = React.useState<undefined | Page>(context.page);
75
+
65
76
  // Bind context object to client router
66
- if (clientRouter !== undefined)
77
+ if (clientRouter !== undefined) {
67
78
  clientRouter.context = context;
68
-
69
- const [pages, setPages] = React.useState<{
70
- current: undefined | Page
71
- }>({
72
- current: context.page
73
- });
79
+ clientRouter.navigate = changePage;
80
+ }
74
81
 
75
82
  /*----------------------------------
76
83
  - ACTIONS
77
84
  ----------------------------------*/
78
- const resolvePage = async (request: ClientRequest, locationUpdate?: Update) => {
85
+ const resolvePage = async (request: ClientRequest, data: {} = {}) => {
79
86
 
80
87
  if (!clientRouter) return;
81
88
 
@@ -85,42 +92,57 @@ export default ({ service: clientRouter }: { service?: ClientRouter }) => {
85
92
  // WARNING: Don"t try to play with pages here, since the object will not be updated
86
93
  // If needed to play with pages, do it in the setPages callback below
87
94
  // Unchanged path
88
- if (request.path === currentRequest.path && request.hash !== currentRequest.hash && request.hash !== undefined) {
95
+ if (
96
+ request.path === currentRequest.path
97
+ &&
98
+ request.hash !== currentRequest.hash
99
+ &&
100
+ request.hash !== undefined
101
+ ) {
89
102
  scrollToElement(request.hash);
90
103
  return;
91
104
  }
92
105
 
93
106
  // Set loading state
107
+ clientRouter.runHook('page.change', request);
108
+ window.scrollTo({
109
+ top: 0,
110
+ behavior: 'smooth'
111
+ });
94
112
  clientRouter.setLoading(true);
95
113
  const newpage = context.page = await clientRouter.resolve(request);
96
114
 
97
- // Page not found: Directly load with the browser
98
- if (newpage === undefined) {
99
- window.location.replace(request.url);
100
- console.error("not found");
101
- return;
102
115
  // Unable to load (no connection, server error, ....)
103
- } else if (newpage === null) {
116
+ if (newpage === null) {
104
117
  return;
105
118
  }
106
119
 
120
+ return await changePage(newpage, data, request);
121
+ }
122
+
123
+ async function changePage(newpage: Page, data?: {}, request?: ClientRequest) {
124
+
107
125
  // Fetch API data to hydrate the page
108
126
  try {
109
127
  await newpage.preRender();
110
128
  } catch (error) {
111
129
  console.error(LogPrefix, "Unable to fetch data:", error);
112
- clientRouter.setLoading(false);
130
+ clientRouter?.setLoading(false);
113
131
  return;
114
132
  }
115
133
 
134
+ // Add additional data
135
+ if (data)
136
+ newpage.data = { ...newpage.data, ...data };
137
+
116
138
  // Add page container
117
- setPages( pages => {
139
+ setCurrentPage( page => {
118
140
 
119
141
  // WARN: Don't cancel navigation if same page as before, as we already instanciated the new page and bound the context with it
120
142
  // Otherwise it would cause reference issues (ex: page.setAllData makes ref to the new context)
121
143
 
122
144
  // If if the layout changed
123
- const curLayout = pages.current?.layout;
145
+ const curLayout = currentPage?.layout;
124
146
  const newLayout = newpage?.layout;
125
147
  if (newLayout && curLayout && newLayout.path !== curLayout.path) {
126
148
 
@@ -129,13 +151,13 @@ export default ({ service: clientRouter }: { service?: ClientRouter }) => {
129
151
  // But when we call setLayout, the style of the previous layout are still oaded and applied
130
152
  // Find a way to unload the previous layout / page resources before to load the new one
131
153
  console.log(LogPrefix, `Changing layout. Before:`, curLayout, 'New layout:', newLayout);
132
- window.location.replace(request.url);
133
- return { ...pages }
154
+ window.location.replace( request ? request.url : location.href );
155
+ return { ...page }
134
156
 
135
157
  context.app.setLayout(newLayout);
136
158
  }
137
159
 
138
- return { current: newpage }
160
+ return newpage;
139
161
  });
140
162
  }
141
163
 
@@ -169,28 +191,28 @@ export default ({ service: clientRouter }: { service?: ClientRouter }) => {
169
191
  // Reset scroll
170
192
  window.scrollTo(0, 0);
171
193
  // Should be called AFTER rendering the page (so after the state change)
172
- pages.current?.updateClient();
194
+ currentPage?.updateClient();
173
195
  // Scroll to the selected content via url hash
174
- restoreScroll(pages.current);
196
+ restoreScroll(currentPage);
175
197
 
176
198
  // Hooks
177
- clientRouter.runHook('page.changed', pages.current)
199
+ clientRouter.runHook('page.changed', currentPage)
178
200
 
179
- }, [pages.current]);
201
+ }, [currentPage]);
180
202
 
181
203
  /*----------------------------------
182
204
  - RENDER
183
205
  ----------------------------------*/
184
206
  // Render the page component
185
207
  return <>
186
- {pages.current && (
187
- <PageComponent page={pages.current}
208
+ {currentPage && (
209
+ <PageComponent page={currentPage}
188
210
  /* Create a new instance of the Page component every time the page change
189
211
  Otherwise the page will memorise the data of the previous page */
190
- key={pages.current.chunkId === undefined ? undefined : 'page_' + pages.current.chunkId}
212
+ key={currentPage.chunkId === undefined ? undefined : 'page_' + currentPage.chunkId}
191
213
  />
192
214
  )}
193
215
 
194
- <PageLoading clientRouter={clientRouter} />
216
+ <PageLoading clientRouter={clientRouter} loaderComponent={loaderComponent} />
195
217
  </>
196
218
  }
@@ -17,7 +17,7 @@ import type { TBasicSSrData } from '@server/services/router/response';
17
17
  import BaseRouter, {
18
18
  defaultOptions, TRoute, TErrorRoute,
19
19
  TClientOrServerContext, TRouteModule,
20
- buildUrl, TDomainsList
20
+ matchRoute, buildUrl, TDomainsList
21
21
  } from '@common/router'
22
22
  import { getLayout } from '@common/router/layouts';
23
23
  import { getRegisterPageArgs, buildRegex } from '@common/router/register';
@@ -123,7 +123,7 @@ export type TRoutesLoaders = {
123
123
 
124
124
  export type THookCallback<TRouter extends ClientRouter> = (request: ClientRequest<TRouter>) => void;
125
125
 
126
- type THookName = 'location.change' | 'page.changed'
126
+ type THookName = 'page.change' | 'page.changed'
127
127
 
128
128
  type Config<TAdditionnalContext extends {} = {}> = {
129
129
  preload: string[], // List of globs
@@ -145,6 +145,7 @@ export default class ClientRouter<
145
145
  public context!: ClientContext;
146
146
 
147
147
  public setLoading!: React.Dispatch< React.SetStateAction<boolean> >;
148
+ public navigate!: (page: ClientPage, data?: {}) => void;
148
149
 
149
150
  public constructor(app: TApplication, config: Config<TAdditionnalContext>) {
150
151
 
@@ -161,10 +162,18 @@ export default class ClientRouter<
161
162
  public url = (path: string, params: {} = {}, absolute: boolean = true) =>
162
163
  buildUrl(path, params, this.domains, absolute);
163
164
 
164
- public go( url: string, data: {} = {}, opt: {
165
+ public go( url: string | number, data: {} = {}, opt: {
165
166
  newTab?: boolean
166
167
  } = {}) {
167
168
 
169
+ // Error code
170
+ if (typeof url === 'number') {
171
+ this.createResponse( this.errors[url], this.context.request ).then(( page ) => {
172
+ this.navigate(page, data);
173
+ })
174
+ return;
175
+ }
176
+
168
177
  url = this.url(url, data, false);
169
178
 
170
179
  if (opt.newTab)
@@ -304,10 +313,9 @@ export default class ClientRouter<
304
313
  /*----------------------------------
305
314
  - RESOLUTION
306
315
  ----------------------------------*/
307
- public async resolve(request: ClientRequest<this>): Promise<ClientPage | undefined | null> {
316
+ public async resolve(request: ClientRequest<this>): Promise<ClientPage> {
308
317
 
309
318
  debug && console.log(LogPrefix, 'Resolving request', request.path, Object.keys(request.data));
310
- this.runHook('location.change', request);
311
319
 
312
320
  for (let iRoute = 0; iRoute < this.routes.length; iRoute++) {
313
321
 
@@ -315,17 +323,10 @@ export default class ClientRouter<
315
323
  if (!('regex' in route))
316
324
  continue;
317
325
 
318
- const match = route.regex.exec(request.path);
319
- if (!match)
326
+ const isMatching = matchRoute(route, request);
327
+ if (!isMatching)
320
328
  continue;
321
329
 
322
- // URL data
323
- for (let iKey = 0; iKey < route.keys.length; iKey++) {
324
- const nomParam = route.keys[iKey];
325
- if (typeof nomParam === 'string') // number = sans nom
326
- request.data[nomParam] = match[iKey + 1]
327
- }
328
-
329
330
  // Create response
330
331
  debug && console.log(LogPrefix, 'Resolved request', request.path, '| Route:', route);
331
332
  const page = await this.createResponse(route, request);
@@ -334,7 +335,10 @@ export default class ClientRouter<
334
335
 
335
336
  };
336
337
 
337
- return undefined;
338
+ console.log("404 error page not found.", this.errors, this.routes);
339
+
340
+ const notFoundRoute = this.errors[404];
341
+ return await this.createResponse(notFoundRoute, request);
338
342
  }
339
343
 
340
344
  private async load(route: TUnresolvedNormalRoute): Promise<TRoute>;
@@ -415,7 +419,7 @@ export default class ClientRouter<
415
419
  }
416
420
 
417
421
  private async createResponse(
418
- route: TUnresolvedRoute | TRoute,
422
+ route: TUnresolvedRoute | TErrorRoute | TRoute,
419
423
  request: ClientRequest<this>,
420
424
  pageData: {} = {}
421
425
  ): Promise<ClientPage> {
@@ -9,7 +9,7 @@ import ApiClientService, {
9
9
  TApiFetchOptions, TFetcherList, TFetcherArgs, TFetcher,
10
10
  TDataReturnedByFetchers
11
11
  } from '@common/router/request/api';
12
- import { viaHttpCode, NetworkError } from '@common/errors';
12
+ import { fromJson as errorFromJson, NetworkError } from '@common/errors';
13
13
  import type ClientApplication from '@client/app';
14
14
 
15
15
  import { toMultipart } from './multipart';
@@ -232,9 +232,10 @@ export default class ApiClient implements ApiClientService {
232
232
  return fetch(url, config)
233
233
  .then(async (response) => {
234
234
  if (!response.ok) {
235
+
235
236
  const errorData = await response.json();
236
237
  console.warn(`[api] Failure:`, response.status, errorData);
237
- const error = viaHttpCode(response.status || 500, errorData);
238
+ const error = errorFromJson(errorData);
238
239
  throw error;
239
240
  }
240
241
  debug && console.log(`[api] Success:`, response);
@@ -27,7 +27,6 @@ export default class ClientRequest<TRouter extends ClientRouter = ClientRouter>
27
27
  public api: ApiClient;
28
28
  public response?: ClientResponse<TRouter>;
29
29
 
30
- public url: string;
31
30
  public hash?: string;
32
31
 
33
32
  public constructor(
@@ -7,16 +7,17 @@ import type { ComponentChild } from 'preact';
7
7
 
8
8
  export type TListeErreursSaisie<TClesDonnees extends string = string> = {[champ in TClesDonnees]: string[]}
9
9
 
10
- export type TReponseApi = {
10
+ type TJsonError = {
11
11
  code: number,
12
- idRapport?: string,
13
- urlRequete?: string
14
- } & ({ message: string } | { errors: TListeErreursSaisie })
12
+ origin?: string,
13
+ message: string,
14
+ // Form fields
15
+ errors?: TListeErreursSaisie
16
+ } & TDetailsErreur
15
17
 
16
18
  type TDetailsErreur = {
17
19
  stack?: string,
18
- idRapport?: string,
19
- urlRequete?: string,
20
+ origin?: string,
20
21
  }
21
22
 
22
23
  /*----------------------------------
@@ -49,9 +50,7 @@ export abstract class CoreError extends Error {
49
50
  public abstract http: number;
50
51
  public title: string = "Uh Oh ...";
51
52
  public message: string;
52
-
53
- public urlRequete?: string;
54
- public idRapport?: string;
53
+ public details: TDetailsErreur = {};
55
54
 
56
55
  // Note: On ne le redéfini pas ici, car déjà présent dans Error
57
56
  // La redéfinition reset la valeur du stacktrace
@@ -62,25 +61,20 @@ export abstract class CoreError extends Error {
62
61
  super(message);
63
62
 
64
63
  this.message = message || (this.constructor as typeof CoreError).msgDefaut;
64
+ this.details = details || {};
65
65
 
66
- if (details !== undefined) {
67
- this.idRapport = details.idRapport;
66
+ // Inject stack
67
+ if (details !== undefined)
68
68
  this.stack = details.stack;
69
- this.urlRequete = details.urlRequete;
70
-
71
- if (this.urlRequete !== undefined)
72
- this.message + '(' + this.urlRequete + ') ' + this.message;
73
- }
74
69
 
75
70
  }
76
71
 
77
- public json(): TReponseApi {
72
+ public json(): TJsonError {
78
73
 
79
74
  return {
80
75
  code: this.http,
81
76
  message: this.message,
82
- idRapport: this.idRapport,
83
- urlRequete: this.urlRequete,
77
+ ...this.details
84
78
  }
85
79
  }
86
80
 
@@ -116,7 +110,7 @@ export class InputErrorSchema extends CoreError {
116
110
 
117
111
  }
118
112
 
119
- public json(): TReponseApi {
113
+ public json(): TJsonError {
120
114
  return {
121
115
  ...super.json(),
122
116
  errors: this.errors,
@@ -182,21 +176,46 @@ export class NetworkError extends Error {
182
176
 
183
177
  export const viaHttpCode = (
184
178
  code: number,
185
- message?: string | TListeErreursSaisie,
179
+ message: string,
186
180
  details?: TDetailsErreur
187
181
  ): CoreError => {
182
+ return fromJson({
183
+ code,
184
+ message,
185
+ ...details
186
+ });
187
+ }
188
+
189
+ export const toJson = (e: Error | CoreError): TJsonError => {
190
+
191
+ if (('json' in e) && typeof e.json === 'function')
192
+ return e.json();
193
+
194
+ const details = ('details' in e)
195
+ ? e.details
196
+ : { stack: e.stack };
188
197
 
189
- // TODO: more reliablme detection of form errors
190
- if (typeof message === 'object')
191
- return new InputErrorSchema(message, details);
198
+ return { code: 500, message: e.message, ...details }
199
+ }
200
+
201
+ export const fromJson = ({ code, message, ...details }: TJsonError) => {
192
202
 
193
203
  switch (code) {
194
- case 400: return new InputError( message, details);
195
- case 401: return new AuthRequired( message, details);
196
- case 403: return new Forbidden( message, details);
197
- case 404: return new NotFound( message, details);
198
- default: return new Anomaly( message, details);
204
+ case 400:
205
+ if (details.errors)
206
+ return new InputErrorSchema( details.errors, details );
207
+ else
208
+ return new InputError( message, details );
209
+
210
+ case 401: return new AuthRequired( message, details );
211
+
212
+ case 403: return new Forbidden( message, details );
213
+
214
+ case 404: return new NotFound( message, details );
215
+
216
+ default: return new Anomaly( message, details );
199
217
  }
218
+
200
219
  }
201
220
 
202
221
  export default CoreError;
@@ -15,6 +15,8 @@ import type {
15
15
  TRouteHttpMethod
16
16
  } from '@server/services/router';
17
17
 
18
+ import type RouterRequest from './request';
19
+
18
20
  import type { TUserRole } from '@server/services/auth';
19
21
 
20
22
  import type { TAppArrowFunction } from '@common/app';
@@ -173,6 +175,24 @@ export const buildUrl = (
173
175
  return prefix + path + (searchParams.toString() ? '?' + searchParams.toString() : '');
174
176
  }
175
177
 
178
+ export const matchRoute = (route: TRoute, request: RouterRequest) => {
179
+
180
+ // Match Path
181
+ const match = route.regex.exec(request.path);
182
+ if (!match)
183
+ return false;
184
+
185
+ // Extract URL params
186
+ for (let iKey = 0; iKey < route.keys.length; iKey++) {
187
+ const key = route.keys[iKey];
188
+ const value = match[iKey + 1];
189
+ if (typeof key === 'string' && value) // number = sans nom
190
+ request.data[key] = decodeURIComponent( value.replaceAll('+', '%20') );
191
+ }
192
+
193
+ return true;
194
+ }
195
+
176
196
  /*----------------------------------
177
197
  - BASE ROUTER
178
198
  ----------------------------------*/
@@ -17,6 +17,7 @@ export default abstract class BaseRequest {
17
17
 
18
18
  // Permet d'accèder à l'instance complète via spread
19
19
  public request: this = this;
20
+ public url!: string;
20
21
  public host!: string;
21
22
 
22
23
  public data: TObjetDonnees = {};
@@ -67,7 +67,6 @@ export default abstract class PageResponse<TRouter extends ClientOrServerRouter
67
67
  public bodyId?: string;
68
68
 
69
69
  // Resources
70
- public amp?: boolean;
71
70
  public scripts: TPageResource[] = [];
72
71
  public style: TPageResource[] = [];
73
72
 
@@ -21,11 +21,11 @@ import Service, { AnyService } from '@server/app/service';
21
21
  import type { TRegisteredServicesIndex } from '@server/app/service/container';
22
22
  import context from '@server/context';
23
23
  import type DisksManager from '@server/services/disks';
24
- import { CoreError, NotFound } from '@common/errors';
24
+ import { CoreError, NotFound, toJson as errorToJson } from '@common/errors';
25
25
  import BaseRouter, {
26
26
  TRoute, TErrorRoute, TRouteModule,
27
27
  TRouteOptions, defaultOptions,
28
- buildUrl, TDomainsList
28
+ matchRoute, buildUrl, TDomainsList
29
29
  } from '@common/router';
30
30
  import { buildRegex, getRegisterPageArgs } from '@common/router/register';
31
31
  import { layoutsList, getLayout } from '@common/router/layouts';
@@ -523,19 +523,10 @@ declare type Routes = {
523
523
  if (!request.accepts(route.options.accept))
524
524
  continue;
525
525
 
526
- // Match Path
527
- const match = route.regex.exec(request.path);
528
- if (!match)
526
+ const isMatching = matchRoute(route, request);
527
+ if (!isMatching)
529
528
  continue;
530
529
 
531
- // Extract URL params
532
- for (let iKey = 0; iKey < route.keys.length; iKey++) {
533
- const key = route.keys[iKey];
534
- const value = match[iKey + 1];
535
- if (typeof key === 'string' && value) // number = sans nom
536
- request.data[key] = decodeURIComponent(value);
537
- }
538
-
539
530
  // Run on resolution hooks. Ex: authentication check
540
531
  await this.runHook('resolved', route);
541
532
 
@@ -552,7 +543,11 @@ declare type Routes = {
552
543
 
553
544
  if (this.app.env.profile === 'dev') {
554
545
  console.log('API batch error:', request.method, request.path, error);
555
- error.message = request.method + ' ' + request.path + ': ' + error.message;
546
+ const errOrigin = request.method + ' ' + request.path;
547
+ if (error.details === undefined)
548
+ error.details = { origin: errOrigin }
549
+ else
550
+ error.details.origin = errOrigin;
556
551
  }
557
552
 
558
553
  throw error;
@@ -609,14 +604,16 @@ declare type Routes = {
609
604
  } else if (code !== 404 && this.app.env.profile === "dev")
610
605
  console.warn(e);
611
606
 
612
- if (request.accepts("html"))
607
+ // Return error based on the request format
608
+ if (request.accepts("html")) {
609
+ const jsonError = errorToJson(e);
613
610
  await response.runController(route, {
614
- message: e.message,
615
- type: e.constructor.name
611
+ error: jsonError
616
612
  });
617
- else if (request.accepts("json"))
618
- await response.json(e.message);
619
- else
613
+ } else if (request.accepts("json")) {
614
+ const jsonError = errorToJson(e);
615
+ await response.json(jsonError);
616
+ } else
620
617
  await response.text(e.message);
621
618
 
622
619
  return response;
@@ -89,8 +89,7 @@ export default class ApiClientRequest extends RequestService implements ApiClien
89
89
  continue;
90
90
 
91
91
  // Create a children request to resolve the api data
92
- const internalHeaders = { accept: 'application/json' }
93
- const request = this.request.children(method, path, data, { ...internalHeaders/*, ...headers*/ });
92
+ const request = this.request.children(method, path, data);
94
93
  fetchedData[id] = await request.router.resolve(request).then(res => res.data);
95
94
  }
96
95
 
@@ -87,6 +87,7 @@ export default class ServerRequest<
87
87
  this.router = router;
88
88
  this.api = new ApiClient(this);
89
89
 
90
+ this.url = this.req.url;
90
91
  this.host = this.req.get('host') as string;
91
92
  this.method = method;
92
93
  this.headers = headers || {};
@@ -99,9 +100,9 @@ export default class ServerRequest<
99
100
  this.data = data || {};
100
101
  }
101
102
 
102
- public children(method: HttpMethod, path: string, data: TObjetDonnees | undefined, headers?: HttpHeaders) {
103
+ public children(method: HttpMethod, path: string, data: TObjetDonnees | undefined) {
103
104
  const children = new ServerRequest(
104
- this.id, method, path, data, { ...(headers || {}), accept: 'application/json' },
105
+ this.id, method, path, data, { ...this.headers, accept: 'application/json' },
105
106
  this.res, this.router, true
106
107
  );
107
108
  children.user = this.user;
@@ -57,31 +57,26 @@ export default class DocumentRenderer<TRouter extends Router> {
57
57
 
58
58
  public async page( html: string, page: Page, response: ServerResponse<TRouter> ) {
59
59
 
60
- const fullUrl = this.router.http.publicUrl + response.request.path;
60
+ // TODO: can be customized via page / route config
61
+ const canonicalUrl = response.request.req.url;
61
62
 
62
63
  let attrsBody = {
63
64
  className: [...page.bodyClass].join(' '),
64
65
  };
65
66
 
66
67
  return '<!doctype html>' + renderToString(
67
- <html lang="en" {...(page.amp ? { amp: "true" } : {})}>
68
+ <html lang="en">
68
69
  <head>
69
70
  {/* Format */}
70
71
  <meta charSet="utf-8" />
71
- {page.amp && ( // As a best practice, you should include the script as early as possible in the <head>.
72
- <script async={true} src="https://cdn.ampproject.org/v0.js"></script>
73
- )}
74
72
  <meta content="IE=edge" httpEquiv="X-UA-Compatible" />
75
73
  <meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1" />
76
- {!page.amp && page.amp && (
77
- <link rel="amphtml" href={fullUrl + '/amp'} />
78
- )}
79
74
 
80
75
  {/* Basique*/}
81
76
  <meta content={this.app.identity.web.title} name="apple-mobile-web-app-title" />
82
77
  <title>{page.title}</title>
83
78
  <meta content={page.description} name="description" />
84
- <link rel="canonical" href={fullUrl} />
79
+ <link rel="canonical" href={canonicalUrl} />
85
80
 
86
81
  {this.metas( page )}
87
82
 
@@ -147,9 +142,6 @@ export default class DocumentRenderer<TRouter extends Router> {
147
142
  </> : <>
148
143
  <style id={style.id} dangerouslySetInnerHTML={{ __html: style.inline }} />
149
144
  </>)}
150
-
151
- {/* Sera remplacé par la chaine exacte après renderToStaticMarkup */}
152
- {page.amp && (<style amp-boilerplate=""></style>)}
153
145
  </>
154
146
  }
155
147
 
@@ -161,13 +153,11 @@ export default class DocumentRenderer<TRouter extends Router> {
161
153
 
162
154
  return <>
163
155
  {/* JS */}
164
- {!page.amp && (
165
- <script type="text/javascript" dangerouslySetInnerHTML={{
166
- __html: `window.ssr=${context}; window.routes=${routesForClient};` + (
167
- this.app.env.profile === 'dev' ? 'window.dev = true;' : ''
168
- )
169
- }} />
170
- )}
156
+ <script type="text/javascript" dangerouslySetInnerHTML={{
157
+ __html: `window.ssr=${context}; window.routes=${routesForClient};` + (
158
+ this.app.env.profile === 'dev' ? 'window.dev = true;' : ''
159
+ )
160
+ }} />
171
161
 
172
162
  <link rel="preload" href={"/public/client.js?v=" + BUILD_ID} as="script" />
173
163
  <script defer type="text/javascript" src={"/public/client.js?v=" + BUILD_ID} />
@@ -83,11 +83,7 @@ export default class Page<TRouter extends Router = Router> extends PageResponse<
83
83
  attrsBody.className += ' ' + page.classeBody.join(' ');
84
84
 
85
85
  if (page.theme)
86
- attrsBody.className += ' ' + page.theme;
87
-
88
- // L'url canonique doit pointer vers la version html
89
- if (page.amp && fullUrl.endsWith('/amp'))
90
- fullUrl = fullUrl.substring(0, fullUrl.length - 4);*/
86
+ attrsBody.className += ' ' + page.theme;*/
91
87
 
92
88
  return this.router.render.page(html, this, this.context.response);
93
89
  }
@@ -113,9 +109,7 @@ export default class Page<TRouter extends Router = Router> extends PageResponse<
113
109
  id: chunk,
114
110
  url: '/public/' + asset
115
111
  })
116
- // Si mode amp, on ne charge pas le JS react (rendu serveur uniquement)
117
- // Sauf si mode dev, car le hot reload est quand même bien pratique ...
118
- else if (!this.amp)
112
+ else
119
113
  this.scripts.push({
120
114
  id: chunk,
121
115
  url: '/public/' + asset