@tramvai/module-server 3.9.1 → 3.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,10 +3,12 @@ import type { LOGGER_TOKEN } from '@tramvai/module-common';
3
3
  import type { FETCH_WEBPACK_STATS_TOKEN } from '@tramvai/tokens-render';
4
4
  import type { WEB_FASTIFY_APP_AFTER_ERROR_TOKEN, WEB_FASTIFY_APP_BEFORE_ERROR_TOKEN } from '@tramvai/tokens-server-private';
5
5
  import type { ExtractDependencyType } from '@tinkoff/dippy';
6
- export declare const errorHandler: (app: FastifyInstance, { log, beforeError, afterError, fetchWebpackStats, }: {
6
+ import type { STATIC_ROOT_ERROR_BOUNDARY_ERROR_TOKEN } from '@tramvai/tokens-server';
7
+ export declare const errorHandler: (app: FastifyInstance, { log, beforeError, afterError, fetchWebpackStats, staticRootErrorBoundaryError, }: {
7
8
  log: ReturnType<typeof LOGGER_TOKEN>;
8
9
  beforeError: ExtractDependencyType<typeof WEB_FASTIFY_APP_BEFORE_ERROR_TOKEN>;
9
10
  afterError: ExtractDependencyType<typeof WEB_FASTIFY_APP_AFTER_ERROR_TOKEN>;
10
11
  fetchWebpackStats: ExtractDependencyType<typeof FETCH_WEBPACK_STATS_TOKEN>;
11
- }) => void;
12
- //# sourceMappingURL=error.d.ts.map
12
+ staticRootErrorBoundaryError: ExtractDependencyType<typeof STATIC_ROOT_ERROR_BOUNDARY_ERROR_TOKEN>;
13
+ }) => Promise<void>;
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,93 @@
1
+ import isNil from '@tinkoff/utils/is/nil';
2
+ import { isRedirectFoundError } from '@tinkoff/errors';
3
+ import { getRootErrorBoundary, getRequestInfo, runHandlersFactory } from './utils.es.js';
4
+ import { serveRootErrorBoundary } from './serveRootErrorBoundary.es.js';
5
+ import { prepareLogsForError } from './prepareLogsForError.es.js';
6
+ import { renderErrorBoundaryPageToString } from './renderErrorBoundaryPageToString.es.js';
7
+
8
+ const errorHandler = async (app, { log, beforeError, afterError, fetchWebpackStats, staticRootErrorBoundaryError, }) => {
9
+ const webpackStats = await fetchWebpackStats();
10
+ const RootErrorBoundary = getRootErrorBoundary();
11
+ const isRootErrorBoundaryExist = RootErrorBoundary !== null;
12
+ if (process.env.TRAMVAI_CLI_COMMAND === 'static' && isRootErrorBoundaryExist) {
13
+ serveRootErrorBoundary({
14
+ response: renderErrorBoundaryPageToString({
15
+ element: RootErrorBoundary,
16
+ requestUrl: '/5xx.html',
17
+ error: staticRootErrorBoundaryError !== null && staticRootErrorBoundaryError !== void 0 ? staticRootErrorBoundaryError : {
18
+ name: 'STATIC_ROOT_ERROR_BOUNDARY_ERROR',
19
+ message: 'Default error for root error boundary',
20
+ },
21
+ httpStatus: 500,
22
+ webpackStats,
23
+ }),
24
+ app,
25
+ });
26
+ }
27
+ app.setErrorHandler(async (error, request, reply) => {
28
+ const runHandlers = runHandlersFactory(error, request, reply);
29
+ const requestInfo = getRequestInfo(request);
30
+ const beforeErrorResult = await runHandlers(beforeError);
31
+ if (!isNil(beforeErrorResult)) {
32
+ return beforeErrorResult;
33
+ }
34
+ if (isRedirectFoundError(error)) {
35
+ log.info({
36
+ event: 'redirect-found-error',
37
+ message: `RedirectFoundError, redirect to ${error.nextUrl}, action execution will be aborted.
38
+ More information about redirects - https://tramvai.dev/docs/features/routing/redirects`,
39
+ error,
40
+ requestInfo,
41
+ });
42
+ reply
43
+ .header('cache-control', 'no-store, no-cache, must-revalidate')
44
+ .redirect(error.httpStatus || 307, error.nextUrl);
45
+ return;
46
+ }
47
+ const { logMessage, logLevel, logEvent, httpStatus } = prepareLogsForError({
48
+ error,
49
+ isRootErrorBoundaryExist,
50
+ });
51
+ log[logLevel]({
52
+ event: logEvent,
53
+ message: logMessage,
54
+ error,
55
+ requestInfo,
56
+ });
57
+ const afterErrorResult = await runHandlers(afterError);
58
+ if (!isNil(afterErrorResult)) {
59
+ return afterErrorResult;
60
+ }
61
+ reply.status(httpStatus);
62
+ if (isRootErrorBoundaryExist) {
63
+ try {
64
+ const response = renderErrorBoundaryPageToString({
65
+ element: RootErrorBoundary,
66
+ requestUrl: requestInfo.url,
67
+ webpackStats,
68
+ error,
69
+ httpStatus,
70
+ });
71
+ log.info({
72
+ event: 'render-root-error-boundary',
73
+ message: 'Render Root Error Boundary for the client',
74
+ });
75
+ reply
76
+ .header('Content-Type', 'text/html; charset=utf-8')
77
+ .header('Content-Length', Buffer.byteLength(response, 'utf8'))
78
+ .header('Cache-Control', 'no-store, no-cache, must-revalidate');
79
+ return response;
80
+ }
81
+ catch (e) {
82
+ log.warn({
83
+ event: 'failed-root-error-boundary',
84
+ message: 'Root Error Boundary rendering failed',
85
+ error: e,
86
+ });
87
+ }
88
+ }
89
+ throw error;
90
+ });
91
+ };
92
+
93
+ export { errorHandler };
@@ -0,0 +1,101 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var isNil = require('@tinkoff/utils/is/nil');
6
+ var errors = require('@tinkoff/errors');
7
+ var utils = require('./utils.js');
8
+ var serveRootErrorBoundary = require('./serveRootErrorBoundary.js');
9
+ var prepareLogsForError = require('./prepareLogsForError.js');
10
+ var renderErrorBoundaryPageToString = require('./renderErrorBoundaryPageToString.js');
11
+
12
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
13
+
14
+ var isNil__default = /*#__PURE__*/_interopDefaultLegacy(isNil);
15
+
16
+ const errorHandler = async (app, { log, beforeError, afterError, fetchWebpackStats, staticRootErrorBoundaryError, }) => {
17
+ const webpackStats = await fetchWebpackStats();
18
+ const RootErrorBoundary = utils.getRootErrorBoundary();
19
+ const isRootErrorBoundaryExist = RootErrorBoundary !== null;
20
+ if (process.env.TRAMVAI_CLI_COMMAND === 'static' && isRootErrorBoundaryExist) {
21
+ serveRootErrorBoundary.serveRootErrorBoundary({
22
+ response: renderErrorBoundaryPageToString.renderErrorBoundaryPageToString({
23
+ element: RootErrorBoundary,
24
+ requestUrl: '/5xx.html',
25
+ error: staticRootErrorBoundaryError !== null && staticRootErrorBoundaryError !== void 0 ? staticRootErrorBoundaryError : {
26
+ name: 'STATIC_ROOT_ERROR_BOUNDARY_ERROR',
27
+ message: 'Default error for root error boundary',
28
+ },
29
+ httpStatus: 500,
30
+ webpackStats,
31
+ }),
32
+ app,
33
+ });
34
+ }
35
+ app.setErrorHandler(async (error, request, reply) => {
36
+ const runHandlers = utils.runHandlersFactory(error, request, reply);
37
+ const requestInfo = utils.getRequestInfo(request);
38
+ const beforeErrorResult = await runHandlers(beforeError);
39
+ if (!isNil__default["default"](beforeErrorResult)) {
40
+ return beforeErrorResult;
41
+ }
42
+ if (errors.isRedirectFoundError(error)) {
43
+ log.info({
44
+ event: 'redirect-found-error',
45
+ message: `RedirectFoundError, redirect to ${error.nextUrl}, action execution will be aborted.
46
+ More information about redirects - https://tramvai.dev/docs/features/routing/redirects`,
47
+ error,
48
+ requestInfo,
49
+ });
50
+ reply
51
+ .header('cache-control', 'no-store, no-cache, must-revalidate')
52
+ .redirect(error.httpStatus || 307, error.nextUrl);
53
+ return;
54
+ }
55
+ const { logMessage, logLevel, logEvent, httpStatus } = prepareLogsForError.prepareLogsForError({
56
+ error,
57
+ isRootErrorBoundaryExist,
58
+ });
59
+ log[logLevel]({
60
+ event: logEvent,
61
+ message: logMessage,
62
+ error,
63
+ requestInfo,
64
+ });
65
+ const afterErrorResult = await runHandlers(afterError);
66
+ if (!isNil__default["default"](afterErrorResult)) {
67
+ return afterErrorResult;
68
+ }
69
+ reply.status(httpStatus);
70
+ if (isRootErrorBoundaryExist) {
71
+ try {
72
+ const response = renderErrorBoundaryPageToString.renderErrorBoundaryPageToString({
73
+ element: RootErrorBoundary,
74
+ requestUrl: requestInfo.url,
75
+ webpackStats,
76
+ error,
77
+ httpStatus,
78
+ });
79
+ log.info({
80
+ event: 'render-root-error-boundary',
81
+ message: 'Render Root Error Boundary for the client',
82
+ });
83
+ reply
84
+ .header('Content-Type', 'text/html; charset=utf-8')
85
+ .header('Content-Length', Buffer.byteLength(response, 'utf8'))
86
+ .header('Cache-Control', 'no-store, no-cache, must-revalidate');
87
+ return response;
88
+ }
89
+ catch (e) {
90
+ log.warn({
91
+ event: 'failed-root-error-boundary',
92
+ message: 'Root Error Boundary rendering failed',
93
+ error: e,
94
+ });
95
+ }
96
+ }
97
+ throw error;
98
+ });
99
+ };
100
+
101
+ exports.errorHandler = errorHandler;
@@ -0,0 +1,11 @@
1
+ import type { FastifyError } from 'fastify';
2
+ export declare const prepareLogsForError: ({ error, isRootErrorBoundaryExist, }: {
3
+ error: FastifyError;
4
+ isRootErrorBoundaryExist: boolean;
5
+ }) => {
6
+ logMessage: string;
7
+ logLevel: string;
8
+ logEvent: string;
9
+ httpStatus: number;
10
+ };
11
+ //# sourceMappingURL=prepareLogsForError.d.ts.map
@@ -0,0 +1,63 @@
1
+ import { isNotFoundError, isHttpError } from '@tinkoff/errors';
2
+
3
+ // eslint-disable-next-line max-statements
4
+ const prepareLogsForError = ({ error, isRootErrorBoundaryExist, }) => {
5
+ let httpStatus;
6
+ let logLevel;
7
+ let logEvent;
8
+ let logMessage;
9
+ if (isNotFoundError(error)) {
10
+ httpStatus = error.httpStatus || 404;
11
+ logLevel = 'info';
12
+ logEvent = 'not-found-error';
13
+ logMessage = `NotFoundError, action execution will be aborted.
14
+ Not Found page is common use-case with this error - https://tramvai.dev/docs/features/routing/wildcard-routes/#not-found-page`;
15
+ }
16
+ else if (isHttpError(error)) {
17
+ httpStatus = error.httpStatus || 500;
18
+ if (error.httpStatus >= 500) {
19
+ logLevel = 'error';
20
+ logEvent = 'send-server-error';
21
+ logMessage = `This is expected server error, here is most common cases:
22
+ - Router Guard blocked request - https://tramvai.dev/docs/features/routing/hooks-and-guards#guards
23
+ - Forced Page Error Boundary render with 5xx code in Guard or Action - https://tramvai.dev/docs/features/error-boundaries#force-render-page-error-boundary-in-action`;
24
+ }
25
+ else {
26
+ logLevel = 'info';
27
+ logEvent = 'http-error';
28
+ logMessage = `This is expected server error, here is most common cases:
29
+ - Route is not found - https://tramvai.dev/docs/features/routing/flow#server-navigation
30
+ - Forced Page Error Boundary render with 4xx code in Guard or Action - https://tramvai.dev/docs/features/error-boundaries#force-render-page-error-boundary-in-action
31
+ - Request Limiter blocked request with 429 code - https://tramvai.dev/docs/references/modules/request-limiter/`;
32
+ }
33
+ }
34
+ else {
35
+ httpStatus = error.statusCode || 500;
36
+ if (error.statusCode >= 500) {
37
+ logLevel = 'error';
38
+ logEvent = 'send-server-error';
39
+ logMessage = `This is Fastify 5xx error, you can check ${error.code} code in https://www.fastify.io/docs/latest/Reference/Errors/#${error.code.toLowerCase()} page`;
40
+ }
41
+ else if (error.statusCode >= 400) {
42
+ // a lot of noise with FST_ERR_CTP_INVALID_MEDIA_TYPE 4xx logs from Fastify,
43
+ // when somebody tries to scan our site and send some unsupported content types
44
+ logLevel = 'info';
45
+ logEvent = 'fastify-error-4xx';
46
+ logMessage = `This is Fastify 4xx error, you can check ${error.code} code in https://www.fastify.io/docs/latest/Reference/Errors/#${error.code.toLowerCase()} page`;
47
+ }
48
+ else {
49
+ logLevel = 'error';
50
+ logEvent = 'send-server-error';
51
+ logMessage = `Unexpected server error. Error cause will be in "error" parameter.
52
+ Most likely an error has occurred in the rendering of the current React page component
53
+ You can try to find relative logs by using "x-request-id" header`;
54
+ }
55
+ }
56
+ logMessage = `${logMessage}
57
+ ${isRootErrorBoundaryExist
58
+ ? 'Root Error Boundary will be rendered for the client'
59
+ : 'You can add Error Boundary for better UX - https://tramvai.dev/docs/features/error-boundaries'}'`;
60
+ return { logMessage, logLevel, logEvent, httpStatus };
61
+ };
62
+
63
+ export { prepareLogsForError };
@@ -0,0 +1,67 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var errors = require('@tinkoff/errors');
6
+
7
+ // eslint-disable-next-line max-statements
8
+ const prepareLogsForError = ({ error, isRootErrorBoundaryExist, }) => {
9
+ let httpStatus;
10
+ let logLevel;
11
+ let logEvent;
12
+ let logMessage;
13
+ if (errors.isNotFoundError(error)) {
14
+ httpStatus = error.httpStatus || 404;
15
+ logLevel = 'info';
16
+ logEvent = 'not-found-error';
17
+ logMessage = `NotFoundError, action execution will be aborted.
18
+ Not Found page is common use-case with this error - https://tramvai.dev/docs/features/routing/wildcard-routes/#not-found-page`;
19
+ }
20
+ else if (errors.isHttpError(error)) {
21
+ httpStatus = error.httpStatus || 500;
22
+ if (error.httpStatus >= 500) {
23
+ logLevel = 'error';
24
+ logEvent = 'send-server-error';
25
+ logMessage = `This is expected server error, here is most common cases:
26
+ - Router Guard blocked request - https://tramvai.dev/docs/features/routing/hooks-and-guards#guards
27
+ - Forced Page Error Boundary render with 5xx code in Guard or Action - https://tramvai.dev/docs/features/error-boundaries#force-render-page-error-boundary-in-action`;
28
+ }
29
+ else {
30
+ logLevel = 'info';
31
+ logEvent = 'http-error';
32
+ logMessage = `This is expected server error, here is most common cases:
33
+ - Route is not found - https://tramvai.dev/docs/features/routing/flow#server-navigation
34
+ - Forced Page Error Boundary render with 4xx code in Guard or Action - https://tramvai.dev/docs/features/error-boundaries#force-render-page-error-boundary-in-action
35
+ - Request Limiter blocked request with 429 code - https://tramvai.dev/docs/references/modules/request-limiter/`;
36
+ }
37
+ }
38
+ else {
39
+ httpStatus = error.statusCode || 500;
40
+ if (error.statusCode >= 500) {
41
+ logLevel = 'error';
42
+ logEvent = 'send-server-error';
43
+ logMessage = `This is Fastify 5xx error, you can check ${error.code} code in https://www.fastify.io/docs/latest/Reference/Errors/#${error.code.toLowerCase()} page`;
44
+ }
45
+ else if (error.statusCode >= 400) {
46
+ // a lot of noise with FST_ERR_CTP_INVALID_MEDIA_TYPE 4xx logs from Fastify,
47
+ // when somebody tries to scan our site and send some unsupported content types
48
+ logLevel = 'info';
49
+ logEvent = 'fastify-error-4xx';
50
+ logMessage = `This is Fastify 4xx error, you can check ${error.code} code in https://www.fastify.io/docs/latest/Reference/Errors/#${error.code.toLowerCase()} page`;
51
+ }
52
+ else {
53
+ logLevel = 'error';
54
+ logEvent = 'send-server-error';
55
+ logMessage = `Unexpected server error. Error cause will be in "error" parameter.
56
+ Most likely an error has occurred in the rendering of the current React page component
57
+ You can try to find relative logs by using "x-request-id" header`;
58
+ }
59
+ }
60
+ logMessage = `${logMessage}
61
+ ${isRootErrorBoundaryExist
62
+ ? 'Root Error Boundary will be rendered for the client'
63
+ : 'You can add Error Boundary for better UX - https://tramvai.dev/docs/features/error-boundaries'}'`;
64
+ return { logMessage, logLevel, logEvent, httpStatus };
65
+ };
66
+
67
+ exports.prepareLogsForError = prepareLogsForError;
@@ -0,0 +1,11 @@
1
+ import type { AnyError } from '@tramvai/safe-strings';
2
+ import type { WebpackStats } from '@tramvai/tokens-render';
3
+ import type { ErrorBoundaryComponent } from '@tramvai/react';
4
+ export declare const renderErrorBoundaryPageToString: ({ element, webpackStats, requestUrl, httpStatus, error, }: {
5
+ element: ErrorBoundaryComponent;
6
+ webpackStats: WebpackStats;
7
+ requestUrl: string;
8
+ httpStatus: number;
9
+ error: AnyError;
10
+ }) => string;
11
+ //# sourceMappingURL=renderErrorBoundaryPageToString.d.ts.map
@@ -0,0 +1,30 @@
1
+ import { ChunkExtractor } from '@loadable/server';
2
+ import { parse } from '@tinkoff/url';
3
+ import { renderToString } from 'react-dom/server';
4
+ import { createElement } from 'react';
5
+ import { safeStringify } from '@tramvai/safe-strings';
6
+
7
+ const renderErrorBoundaryPageToString = ({ element, webpackStats, requestUrl, httpStatus, error, }) => {
8
+ const extractor = new ChunkExtractor({ stats: webpackStats, entrypoints: ['rootErrorBoundary'] });
9
+ const url = parse(requestUrl);
10
+ const serializedError = {
11
+ name: error.name,
12
+ status: httpStatus,
13
+ message: error.message,
14
+ stack: error.stack,
15
+ };
16
+ return renderToString(createElement(element, { error: serializedError, url })).replace('</head>', [
17
+ '<script>' +
18
+ `window.serverUrl = ${safeStringify(url)};` +
19
+ `window.serverError = new Error(${safeStringify(serializedError.message)});` +
20
+ `Object.assign(window.serverError, ${safeStringify(serializedError)});` +
21
+ '</script>',
22
+ extractor.getStyleTags(),
23
+ extractor.getScriptTags(),
24
+ '</head>',
25
+ ]
26
+ .filter(Boolean)
27
+ .join('\n'));
28
+ };
29
+
30
+ export { renderErrorBoundaryPageToString };
@@ -0,0 +1,34 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var server = require('@loadable/server');
6
+ var url = require('@tinkoff/url');
7
+ var server$1 = require('react-dom/server');
8
+ var react = require('react');
9
+ var safeStrings = require('@tramvai/safe-strings');
10
+
11
+ const renderErrorBoundaryPageToString = ({ element, webpackStats, requestUrl, httpStatus, error, }) => {
12
+ const extractor = new server.ChunkExtractor({ stats: webpackStats, entrypoints: ['rootErrorBoundary'] });
13
+ const url$1 = url.parse(requestUrl);
14
+ const serializedError = {
15
+ name: error.name,
16
+ status: httpStatus,
17
+ message: error.message,
18
+ stack: error.stack,
19
+ };
20
+ return server$1.renderToString(react.createElement(element, { error: serializedError, url: url$1 })).replace('</head>', [
21
+ '<script>' +
22
+ `window.serverUrl = ${safeStrings.safeStringify(url$1)};` +
23
+ `window.serverError = new Error(${safeStrings.safeStringify(serializedError.message)});` +
24
+ `Object.assign(window.serverError, ${safeStrings.safeStringify(serializedError)});` +
25
+ '</script>',
26
+ extractor.getStyleTags(),
27
+ extractor.getScriptTags(),
28
+ '</head>',
29
+ ]
30
+ .filter(Boolean)
31
+ .join('\n'));
32
+ };
33
+
34
+ exports.renderErrorBoundaryPageToString = renderErrorBoundaryPageToString;
@@ -0,0 +1,6 @@
1
+ import type { FastifyInstance } from 'fastify';
2
+ export declare function serveRootErrorBoundary({ response, app, }: {
3
+ response: string;
4
+ app: FastifyInstance;
5
+ }): void;
6
+ //# sourceMappingURL=serveRootErrorBoundary.d.ts.map
@@ -0,0 +1,13 @@
1
+ function serveRootErrorBoundary({ response, app, }) {
2
+ app.register(async (instance) => {
3
+ instance.all('/_errors/5xx', (request, reply) => {
4
+ reply.status(200);
5
+ reply.header('Content-Type', 'text/html; charset=utf-8');
6
+ reply.header('Content-Length', Buffer.byteLength(response, 'utf8'));
7
+ reply.header('Cache-Control', 'no-store, no-cache, must-revalidate');
8
+ return response;
9
+ });
10
+ });
11
+ }
12
+
13
+ export { serveRootErrorBoundary };
@@ -0,0 +1,17 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ function serveRootErrorBoundary({ response, app, }) {
6
+ app.register(async (instance) => {
7
+ instance.all('/_errors/5xx', (request, reply) => {
8
+ reply.status(200);
9
+ reply.header('Content-Type', 'text/html; charset=utf-8');
10
+ reply.header('Content-Length', Buffer.byteLength(response, 'utf8'));
11
+ reply.header('Cache-Control', 'no-store, no-cache, must-revalidate');
12
+ return response;
13
+ });
14
+ });
15
+ }
16
+
17
+ exports.serveRootErrorBoundary = serveRootErrorBoundary;
@@ -0,0 +1,12 @@
1
+ import type { ErrorBoundaryComponent } from '@tramvai/react';
2
+ import type { ExtractDependencyType } from '@tinkoff/dippy';
3
+ import type { WEB_FASTIFY_APP_BEFORE_ERROR_TOKEN } from '@tramvai/tokens-server-private';
4
+ import type { FastifyError, FastifyReply, FastifyRequest } from 'fastify';
5
+ export declare const getRootErrorBoundary: () => ErrorBoundaryComponent | null;
6
+ export declare const runHandlersFactory: (error: FastifyError, request: FastifyRequest, reply: FastifyReply) => (handlers: ExtractDependencyType<typeof WEB_FASTIFY_APP_BEFORE_ERROR_TOKEN>) => Promise<string>;
7
+ export declare const getRequestInfo: (request: FastifyRequest) => {
8
+ ip: string;
9
+ requestId: string | string[];
10
+ url: string;
11
+ };
12
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1,32 @@
1
+ const getRootErrorBoundary = () => {
2
+ try {
3
+ // In case of direct `require` by path, e.g.
4
+ // `require(path.resolve(process.cwd(), 'src', 'error.tsx'))` file
5
+ // doesn't include in the bundle, that is why we are using a
6
+ // path alias here along with webpack config option.
7
+ // See usage of `ROOT_ERROR_BOUNDARY_ALIAS`.
8
+ // eslint-disable-next-line import/no-unresolved, import/extensions
9
+ const RootErrorBoundary = require('@/__private__/error').default;
10
+ return RootErrorBoundary;
11
+ }
12
+ catch {
13
+ return null;
14
+ }
15
+ };
16
+ const runHandlersFactory = (error, request, reply) => async (handlers) => {
17
+ if (handlers) {
18
+ for (const handler of handlers) {
19
+ const result = await handler(error, request, reply);
20
+ if (result) {
21
+ return result;
22
+ }
23
+ }
24
+ }
25
+ };
26
+ const getRequestInfo = (request) => ({
27
+ ip: request.ip,
28
+ requestId: request.headers['x-request-id'],
29
+ url: request.url,
30
+ });
31
+
32
+ export { getRequestInfo, getRootErrorBoundary, runHandlersFactory };
@@ -0,0 +1,38 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ const getRootErrorBoundary = () => {
6
+ try {
7
+ // In case of direct `require` by path, e.g.
8
+ // `require(path.resolve(process.cwd(), 'src', 'error.tsx'))` file
9
+ // doesn't include in the bundle, that is why we are using a
10
+ // path alias here along with webpack config option.
11
+ // See usage of `ROOT_ERROR_BOUNDARY_ALIAS`.
12
+ // eslint-disable-next-line import/no-unresolved, import/extensions
13
+ const RootErrorBoundary = require('@/__private__/error').default;
14
+ return RootErrorBoundary;
15
+ }
16
+ catch {
17
+ return null;
18
+ }
19
+ };
20
+ const runHandlersFactory = (error, request, reply) => async (handlers) => {
21
+ if (handlers) {
22
+ for (const handler of handlers) {
23
+ const result = await handler(error, request, reply);
24
+ if (result) {
25
+ return result;
26
+ }
27
+ }
28
+ }
29
+ };
30
+ const getRequestInfo = (request) => ({
31
+ ip: request.ip,
32
+ requestId: request.headers['x-request-id'],
33
+ url: request.url,
34
+ });
35
+
36
+ exports.getRequestInfo = getRequestInfo;
37
+ exports.getRootErrorBoundary = getRootErrorBoundary;
38
+ exports.runHandlersFactory = runHandlersFactory;
@@ -5,10 +5,11 @@ import type { SERVER_TOKEN } from '@tramvai/tokens-server';
5
5
  import type { WEB_FASTIFY_APP_TOKEN, WEB_FASTIFY_APP_AFTER_INIT_TOKEN, WEB_FASTIFY_APP_BEFORE_INIT_TOKEN, WEB_FASTIFY_APP_INIT_TOKEN, WEB_FASTIFY_APP_LIMITER_TOKEN, WEB_FASTIFY_APP_BEFORE_ERROR_TOKEN, WEB_FASTIFY_APP_AFTER_ERROR_TOKEN, WEB_FASTIFY_APP_METRICS_TOKEN } from '@tramvai/tokens-server-private';
6
6
  import { type FETCH_WEBPACK_STATS_TOKEN } from '@tramvai/tokens-render';
7
7
  import type { ExtractDependencyType } from '@tinkoff/dippy';
8
+ import type { STATIC_ROOT_ERROR_BOUNDARY_ERROR_TOKEN } from '@tramvai/tokens-server';
8
9
  export declare const webAppFactory: ({ server }: {
9
10
  server: typeof SERVER_TOKEN;
10
11
  }) => import("fastify").FastifyInstance<import("http").Server<typeof import("http").IncomingMessage, typeof import("http").ServerResponse>, import("http").IncomingMessage, import("http").ServerResponse<import("http").IncomingMessage>, import("fastify").FastifyBaseLogger, import("fastify").FastifyTypeProviderDefault> & PromiseLike<import("fastify").FastifyInstance<import("http").Server<typeof import("http").IncomingMessage, typeof import("http").ServerResponse>, import("http").IncomingMessage, import("http").ServerResponse<import("http").IncomingMessage>, import("fastify").FastifyBaseLogger, import("fastify").FastifyTypeProviderDefault>>;
11
- export declare const webAppInitCommand: ({ app, logger, commandLineRunner, executionContextManager, beforeInit, requestMetrics, limiterRequest, init, afterInit, beforeError, afterError, fetchWebpackStats, }: {
12
+ export declare const webAppInitCommand: ({ app, logger, commandLineRunner, executionContextManager, beforeInit, requestMetrics, limiterRequest, init, afterInit, beforeError, afterError, fetchWebpackStats, staticRootErrorBoundaryError, }: {
12
13
  app: ExtractDependencyType<typeof WEB_FASTIFY_APP_TOKEN>;
13
14
  logger: ExtractDependencyType<typeof LOGGER_TOKEN>;
14
15
  commandLineRunner: ExtractDependencyType<typeof COMMAND_LINE_RUNNER_TOKEN>;
@@ -21,5 +22,6 @@ export declare const webAppInitCommand: ({ app, logger, commandLineRunner, execu
21
22
  beforeError: ExtractDependencyType<typeof WEB_FASTIFY_APP_BEFORE_ERROR_TOKEN>;
22
23
  afterError: ExtractDependencyType<typeof WEB_FASTIFY_APP_AFTER_ERROR_TOKEN>;
23
24
  fetchWebpackStats: ExtractDependencyType<typeof FETCH_WEBPACK_STATS_TOKEN>;
25
+ staticRootErrorBoundaryError: ExtractDependencyType<typeof STATIC_ROOT_ERROR_BOUNDARY_ERROR_TOKEN>;
24
26
  }) => () => Promise<void>;
25
27
  //# sourceMappingURL=webApp.d.ts.map
@@ -6,7 +6,7 @@ import { Scope } from '@tramvai/core';
6
6
  import { FASTIFY_REQUEST, FASTIFY_RESPONSE, SERVER_RESPONSE_STREAM, SERVER_RESPONSE_TASK_MANAGER } from '@tramvai/tokens-server-private';
7
7
  import { REACT_SERVER_RENDER_MODE } from '@tramvai/tokens-render';
8
8
  import { provide, optional } from '@tinkoff/dippy';
9
- import { errorHandler } from './error.es.js';
9
+ import { errorHandler } from './error/index.es.js';
10
10
 
11
11
  const webAppFactory = ({ server }) => {
12
12
  const app = fastify({
@@ -19,13 +19,19 @@ const webAppFactory = ({ server }) => {
19
19
  });
20
20
  return app;
21
21
  };
22
- const webAppInitCommand = ({ app, logger, commandLineRunner, executionContextManager, beforeInit, requestMetrics, limiterRequest, init, afterInit, beforeError, afterError, fetchWebpackStats, }) => {
22
+ const webAppInitCommand = ({ app, logger, commandLineRunner, executionContextManager, beforeInit, requestMetrics, limiterRequest, init, afterInit, beforeError, afterError, fetchWebpackStats, staticRootErrorBoundaryError, }) => {
23
23
  const log = logger('server:webapp');
24
24
  const runHandlers = (instance, handlers) => {
25
25
  return Promise.all([handlers && Promise.all(handlers.map((handler) => handler(instance)))]);
26
26
  };
27
27
  return async function webAppInit() {
28
- errorHandler(app, { log, beforeError, afterError, fetchWebpackStats });
28
+ await errorHandler(app, {
29
+ log,
30
+ beforeError,
31
+ afterError,
32
+ fetchWebpackStats,
33
+ staticRootErrorBoundaryError,
34
+ });
29
35
  await app.register(async (instance) => {
30
36
  await runHandlers(instance, beforeInit);
31
37
  });
@@ -10,7 +10,7 @@ var core = require('@tramvai/core');
10
10
  var tokensServerPrivate = require('@tramvai/tokens-server-private');
11
11
  var tokensRender = require('@tramvai/tokens-render');
12
12
  var dippy = require('@tinkoff/dippy');
13
- var error = require('./error.js');
13
+ var index = require('./error/index.js');
14
14
 
15
15
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
16
16
 
@@ -28,13 +28,19 @@ const webAppFactory = ({ server }) => {
28
28
  });
29
29
  return app;
30
30
  };
31
- const webAppInitCommand = ({ app, logger, commandLineRunner, executionContextManager, beforeInit, requestMetrics, limiterRequest, init, afterInit, beforeError, afterError, fetchWebpackStats, }) => {
31
+ const webAppInitCommand = ({ app, logger, commandLineRunner, executionContextManager, beforeInit, requestMetrics, limiterRequest, init, afterInit, beforeError, afterError, fetchWebpackStats, staticRootErrorBoundaryError, }) => {
32
32
  const log = logger('server:webapp');
33
33
  const runHandlers = (instance, handlers) => {
34
34
  return Promise.all([handlers && Promise.all(handlers.map((handler) => handler(instance)))]);
35
35
  };
36
36
  return async function webAppInit() {
37
- error.errorHandler(app, { log, beforeError, afterError, fetchWebpackStats });
37
+ await index.errorHandler(app, {
38
+ log,
39
+ beforeError,
40
+ afterError,
41
+ fetchWebpackStats,
42
+ staticRootErrorBoundaryError,
43
+ });
38
44
  await app.register(async (instance) => {
39
45
  await runHandlers(instance, beforeInit);
40
46
  });
package/lib/server.es.js CHANGED
@@ -2,7 +2,7 @@ import { __decorate } from 'tslib';
2
2
  import { setDefaultResultOrder } from 'dns';
3
3
  import EventEmitter from 'events';
4
4
  import { Module, provide, Scope, commandLineListTokens, COMMAND_LINE_RUNNER_TOKEN, APP_INFO_TOKEN } from '@tramvai/core';
5
- import { SERVER_TOKEN } from '@tramvai/tokens-server';
5
+ import { SERVER_TOKEN, STATIC_ROOT_ERROR_BOUNDARY_ERROR_TOKEN } from '@tramvai/tokens-server';
6
6
  export * from '@tramvai/tokens-server';
7
7
  import { FETCH_WEBPACK_STATS_TOKEN } from '@tramvai/tokens-render';
8
8
  import { SERVER_FACTORY_TOKEN, WEB_FASTIFY_APP_FACTORY_TOKEN, WEB_FASTIFY_APP_TOKEN, WEB_FASTIFY_APP_BEFORE_INIT_TOKEN, WEB_FASTIFY_APP_INIT_TOKEN, WEB_FASTIFY_APP_AFTER_INIT_TOKEN, WEB_FASTIFY_APP_METRICS_TOKEN, WEB_FASTIFY_APP_LIMITER_TOKEN, WEB_FASTIFY_APP_BEFORE_ERROR_TOKEN, WEB_FASTIFY_APP_AFTER_ERROR_TOKEN, SERVER_RESPONSE_STREAM, SERVER_RESPONSE_TASK_MANAGER } from '@tramvai/tokens-server-private';
@@ -97,6 +97,10 @@ ServerModule = __decorate([
97
97
  beforeError: { token: WEB_FASTIFY_APP_BEFORE_ERROR_TOKEN, optional: true },
98
98
  afterError: { token: WEB_FASTIFY_APP_AFTER_ERROR_TOKEN, optional: true },
99
99
  fetchWebpackStats: FETCH_WEBPACK_STATS_TOKEN,
100
+ staticRootErrorBoundaryError: {
101
+ token: STATIC_ROOT_ERROR_BOUNDARY_ERROR_TOKEN,
102
+ optional: true,
103
+ },
100
104
  },
101
105
  },
102
106
  provide({
package/lib/server.js CHANGED
@@ -104,6 +104,10 @@ exports.ServerModule = tslib.__decorate([
104
104
  beforeError: { token: tokensServerPrivate.WEB_FASTIFY_APP_BEFORE_ERROR_TOKEN, optional: true },
105
105
  afterError: { token: tokensServerPrivate.WEB_FASTIFY_APP_AFTER_ERROR_TOKEN, optional: true },
106
106
  fetchWebpackStats: tokensRender.FETCH_WEBPACK_STATS_TOKEN,
107
+ staticRootErrorBoundaryError: {
108
+ token: tokensServer.STATIC_ROOT_ERROR_BOUNDARY_ERROR_TOKEN,
109
+ optional: true,
110
+ },
107
111
  },
108
112
  },
109
113
  core.provide({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tramvai/module-server",
3
- "version": "3.9.1",
3
+ "version": "3.10.2",
4
4
  "description": "",
5
5
  "browser": "lib/browser.js",
6
6
  "main": "lib/server.js",
@@ -25,13 +25,13 @@
25
25
  "@tinkoff/errors": "0.4.1",
26
26
  "@tinkoff/monkeypatch": "3.0.1",
27
27
  "@tinkoff/terminus": "0.2.1",
28
- "@tinkoff/url": "0.9.1",
29
- "@tramvai/module-cache-warmup": "3.9.1",
30
- "@tramvai/module-metrics": "3.9.1",
31
- "@tramvai/papi": "3.9.1",
32
- "@tramvai/tokens-server": "3.9.1",
33
- "@tramvai/tokens-router": "3.9.1",
34
- "@tramvai/tokens-server-private": "3.9.1",
28
+ "@tinkoff/url": "0.9.2",
29
+ "@tramvai/module-cache-warmup": "3.10.2",
30
+ "@tramvai/module-metrics": "3.10.2",
31
+ "@tramvai/papi": "3.10.2",
32
+ "@tramvai/tokens-server": "3.10.2",
33
+ "@tramvai/tokens-router": "3.10.2",
34
+ "@tramvai/tokens-server-private": "3.10.2",
35
35
  "@tramvai/safe-strings": "0.6.1",
36
36
  "fastify": "^4.14.1",
37
37
  "@fastify/cookie": "^8.3.0",
@@ -44,14 +44,14 @@
44
44
  "peerDependencies": {
45
45
  "@tinkoff/dippy": "0.9.1",
46
46
  "@tinkoff/utils": "^2.1.2",
47
- "@tramvai/cli": "3.9.1",
48
- "@tramvai/core": "3.9.1",
49
- "@tramvai/react": "3.9.1",
50
- "@tramvai/module-common": "3.9.1",
51
- "@tramvai/module-environment": "3.9.1",
52
- "@tramvai/tokens-common": "3.9.1",
53
- "@tramvai/tokens-core-private": "3.9.1",
54
- "@tramvai/tokens-render": "3.9.1",
47
+ "@tramvai/cli": "3.10.2",
48
+ "@tramvai/core": "3.10.2",
49
+ "@tramvai/react": "3.10.2",
50
+ "@tramvai/module-common": "3.10.2",
51
+ "@tramvai/module-environment": "3.10.2",
52
+ "@tramvai/tokens-common": "3.10.2",
53
+ "@tramvai/tokens-core-private": "3.10.2",
54
+ "@tramvai/tokens-render": "3.10.2",
55
55
  "react": ">=16.14.0",
56
56
  "react-dom": ">=16.14.0",
57
57
  "tslib": "^2.4.0"
@@ -1,163 +0,0 @@
1
- import isNil from '@tinkoff/utils/is/nil';
2
- import { createElement } from 'react';
3
- import { renderToString } from 'react-dom/server';
4
- import { parse } from '@tinkoff/url';
5
- import { isRedirectFoundError, isNotFoundError, isHttpError } from '@tinkoff/errors';
6
- import { safeStringify } from '@tramvai/safe-strings';
7
- import { ChunkExtractor } from '@loadable/server';
8
-
9
- const errorHandler = (app, { log, beforeError, afterError, fetchWebpackStats, }) => {
10
- // eslint-disable-next-line max-statements
11
- app.setErrorHandler(async (error, request, reply) => {
12
- const runHandlers = async (handlers) => {
13
- if (handlers) {
14
- for (const handler of handlers) {
15
- const result = await handler(error, request, reply);
16
- if (result) {
17
- return result;
18
- }
19
- }
20
- }
21
- };
22
- const beforeErrorResult = await runHandlers(beforeError);
23
- if (!isNil(beforeErrorResult)) {
24
- return beforeErrorResult;
25
- }
26
- const requestInfo = {
27
- ip: request.ip,
28
- requestId: request.headers['x-request-id'],
29
- url: request.url,
30
- };
31
- let RootErrorBoundary = null;
32
- try {
33
- // In case of direct `require` by path, e.g.
34
- // `require(path.resolve(process.cwd(), 'src', 'error.tsx'))` file
35
- // doesn't include in the bundle, that is why we are using a
36
- // path alias here along with webpack config option.
37
- // See usage of `ROOT_ERROR_BOUNDARY_ALIAS`.
38
- // eslint-disable-next-line import/no-unresolved, import/extensions
39
- RootErrorBoundary = require('@/__private__/error').default;
40
- }
41
- catch { }
42
- if (isRedirectFoundError(error)) {
43
- log.info({
44
- event: 'redirect-found-error',
45
- message: `RedirectFoundError, redirect to ${error.nextUrl}, action execution will be aborted.
46
- More information about redirects - https://tramvai.dev/docs/features/routing/redirects`,
47
- error,
48
- requestInfo,
49
- });
50
- reply.header('cache-control', 'no-store, no-cache, must-revalidate');
51
- reply.redirect(error.httpStatus || 307, error.nextUrl);
52
- return;
53
- }
54
- let httpStatus;
55
- let logLevel;
56
- let logEvent;
57
- let logMessage;
58
- if (isNotFoundError(error)) {
59
- httpStatus = error.httpStatus || 404;
60
- logLevel = 'info';
61
- logEvent = 'not-found-error';
62
- logMessage = `NotFoundError, action execution will be aborted.
63
- Not Found page is common use-case with this error - https://tramvai.dev/docs/features/routing/wildcard-routes/#not-found-page`;
64
- }
65
- else if (isHttpError(error)) {
66
- httpStatus = error.httpStatus || 500;
67
- if (error.httpStatus >= 500) {
68
- logLevel = 'error';
69
- logEvent = 'send-server-error';
70
- logMessage = `This is expected server error, here is most common cases:
71
- - Router Guard blocked request - https://tramvai.dev/docs/features/routing/hooks-and-guards#guards
72
- - Forced Page Error Boundary render with 5xx code in Guard or Action - https://tramvai.dev/docs/features/error-boundaries#force-render-page-error-boundary-in-action`;
73
- }
74
- else {
75
- logLevel = 'info';
76
- logEvent = 'http-error';
77
- logMessage = `This is expected server error, here is most common cases:
78
- - Route is not found - https://tramvai.dev/docs/features/routing/flow#server-navigation
79
- - Forced Page Error Boundary render with 4xx code in Guard or Action - https://tramvai.dev/docs/features/error-boundaries#force-render-page-error-boundary-in-action
80
- - Request Limiter blocked request with 429 code - https://tramvai.dev/docs/references/modules/request-limiter/`;
81
- }
82
- }
83
- else {
84
- httpStatus = error.statusCode || 500;
85
- if (error.statusCode >= 500) {
86
- logLevel = 'error';
87
- logEvent = 'send-server-error';
88
- logMessage = `This is Fastify 5xx error, you can check ${error.code} code in https://www.fastify.io/docs/latest/Reference/Errors/#${error.code.toLowerCase()} page`;
89
- }
90
- else if (error.statusCode >= 400) {
91
- // a lot of noise with FST_ERR_CTP_INVALID_MEDIA_TYPE 4xx logs from Fastify,
92
- // when somebody tries to scan our site and send some unsupported content types
93
- logLevel = 'info';
94
- logEvent = 'fastify-error-4xx';
95
- logMessage = `This is Fastify 4xx error, you can check ${error.code} code in https://www.fastify.io/docs/latest/Reference/Errors/#${error.code.toLowerCase()} page`;
96
- }
97
- else {
98
- logLevel = 'error';
99
- logEvent = 'send-server-error';
100
- logMessage = `Unexpected server error. Error cause will be in "error" parameter.
101
- Most likely an error has occurred in the rendering of the current React page component
102
- You can try to find relative logs by using "x-request-id" header`;
103
- }
104
- }
105
- logMessage = `${logMessage}
106
- ${RootErrorBoundary !== null
107
- ? 'Root Error Boundary will be rendered for the client'
108
- : 'You can add Error Boundary for better UX - https://tramvai.dev/docs/features/error-boundaries'}'`;
109
- log[logLevel]({
110
- event: logEvent,
111
- message: logMessage,
112
- error,
113
- requestInfo,
114
- });
115
- const afterErrorResult = await runHandlers(afterError);
116
- if (!isNil(afterErrorResult)) {
117
- return afterErrorResult;
118
- }
119
- reply.status(httpStatus);
120
- if (RootErrorBoundary !== null) {
121
- try {
122
- const stats = await fetchWebpackStats();
123
- const extractor = new ChunkExtractor({ stats, entrypoints: ['rootErrorBoundary'] });
124
- const url = parse(requestInfo.url);
125
- const serializedError = {
126
- status: httpStatus,
127
- message: error.message,
128
- stack: error.stack,
129
- };
130
- const body = renderToString(createElement(RootErrorBoundary, { error: serializedError, url })).replace('</head>', [
131
- '<script>' +
132
- `window.serverUrl = ${safeStringify(url)};` +
133
- `window.serverError = new Error(${safeStringify(serializedError.message)});` +
134
- `Object.assign(window.serverError, ${safeStringify(serializedError)});` +
135
- '</script>',
136
- extractor.getStyleTags(),
137
- extractor.getScriptTags(),
138
- '</head>',
139
- ]
140
- .filter(Boolean)
141
- .join('\n'));
142
- log.info({
143
- event: 'render-root-error-boundary',
144
- message: 'Render Root Error Boundary for the client',
145
- });
146
- reply.header('Content-Type', 'text/html; charset=utf-8');
147
- reply.header('Content-Length', Buffer.byteLength(body, 'utf8'));
148
- reply.header('Cache-Control', 'no-store, no-cache, must-revalidate');
149
- return body;
150
- }
151
- catch (e) {
152
- log.warn({
153
- event: 'failed-root-error-boundary',
154
- message: 'Root Error Boundary rendering failed',
155
- error: e,
156
- });
157
- }
158
- }
159
- throw error;
160
- });
161
- };
162
-
163
- export { errorHandler };
@@ -1,171 +0,0 @@
1
- 'use strict';
2
-
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
- var isNil = require('@tinkoff/utils/is/nil');
6
- var react = require('react');
7
- var server$1 = require('react-dom/server');
8
- var url = require('@tinkoff/url');
9
- var errors = require('@tinkoff/errors');
10
- var safeStrings = require('@tramvai/safe-strings');
11
- var server = require('@loadable/server');
12
-
13
- function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
14
-
15
- var isNil__default = /*#__PURE__*/_interopDefaultLegacy(isNil);
16
-
17
- const errorHandler = (app, { log, beforeError, afterError, fetchWebpackStats, }) => {
18
- // eslint-disable-next-line max-statements
19
- app.setErrorHandler(async (error, request, reply) => {
20
- const runHandlers = async (handlers) => {
21
- if (handlers) {
22
- for (const handler of handlers) {
23
- const result = await handler(error, request, reply);
24
- if (result) {
25
- return result;
26
- }
27
- }
28
- }
29
- };
30
- const beforeErrorResult = await runHandlers(beforeError);
31
- if (!isNil__default["default"](beforeErrorResult)) {
32
- return beforeErrorResult;
33
- }
34
- const requestInfo = {
35
- ip: request.ip,
36
- requestId: request.headers['x-request-id'],
37
- url: request.url,
38
- };
39
- let RootErrorBoundary = null;
40
- try {
41
- // In case of direct `require` by path, e.g.
42
- // `require(path.resolve(process.cwd(), 'src', 'error.tsx'))` file
43
- // doesn't include in the bundle, that is why we are using a
44
- // path alias here along with webpack config option.
45
- // See usage of `ROOT_ERROR_BOUNDARY_ALIAS`.
46
- // eslint-disable-next-line import/no-unresolved, import/extensions
47
- RootErrorBoundary = require('@/__private__/error').default;
48
- }
49
- catch { }
50
- if (errors.isRedirectFoundError(error)) {
51
- log.info({
52
- event: 'redirect-found-error',
53
- message: `RedirectFoundError, redirect to ${error.nextUrl}, action execution will be aborted.
54
- More information about redirects - https://tramvai.dev/docs/features/routing/redirects`,
55
- error,
56
- requestInfo,
57
- });
58
- reply.header('cache-control', 'no-store, no-cache, must-revalidate');
59
- reply.redirect(error.httpStatus || 307, error.nextUrl);
60
- return;
61
- }
62
- let httpStatus;
63
- let logLevel;
64
- let logEvent;
65
- let logMessage;
66
- if (errors.isNotFoundError(error)) {
67
- httpStatus = error.httpStatus || 404;
68
- logLevel = 'info';
69
- logEvent = 'not-found-error';
70
- logMessage = `NotFoundError, action execution will be aborted.
71
- Not Found page is common use-case with this error - https://tramvai.dev/docs/features/routing/wildcard-routes/#not-found-page`;
72
- }
73
- else if (errors.isHttpError(error)) {
74
- httpStatus = error.httpStatus || 500;
75
- if (error.httpStatus >= 500) {
76
- logLevel = 'error';
77
- logEvent = 'send-server-error';
78
- logMessage = `This is expected server error, here is most common cases:
79
- - Router Guard blocked request - https://tramvai.dev/docs/features/routing/hooks-and-guards#guards
80
- - Forced Page Error Boundary render with 5xx code in Guard or Action - https://tramvai.dev/docs/features/error-boundaries#force-render-page-error-boundary-in-action`;
81
- }
82
- else {
83
- logLevel = 'info';
84
- logEvent = 'http-error';
85
- logMessage = `This is expected server error, here is most common cases:
86
- - Route is not found - https://tramvai.dev/docs/features/routing/flow#server-navigation
87
- - Forced Page Error Boundary render with 4xx code in Guard or Action - https://tramvai.dev/docs/features/error-boundaries#force-render-page-error-boundary-in-action
88
- - Request Limiter blocked request with 429 code - https://tramvai.dev/docs/references/modules/request-limiter/`;
89
- }
90
- }
91
- else {
92
- httpStatus = error.statusCode || 500;
93
- if (error.statusCode >= 500) {
94
- logLevel = 'error';
95
- logEvent = 'send-server-error';
96
- logMessage = `This is Fastify 5xx error, you can check ${error.code} code in https://www.fastify.io/docs/latest/Reference/Errors/#${error.code.toLowerCase()} page`;
97
- }
98
- else if (error.statusCode >= 400) {
99
- // a lot of noise with FST_ERR_CTP_INVALID_MEDIA_TYPE 4xx logs from Fastify,
100
- // when somebody tries to scan our site and send some unsupported content types
101
- logLevel = 'info';
102
- logEvent = 'fastify-error-4xx';
103
- logMessage = `This is Fastify 4xx error, you can check ${error.code} code in https://www.fastify.io/docs/latest/Reference/Errors/#${error.code.toLowerCase()} page`;
104
- }
105
- else {
106
- logLevel = 'error';
107
- logEvent = 'send-server-error';
108
- logMessage = `Unexpected server error. Error cause will be in "error" parameter.
109
- Most likely an error has occurred in the rendering of the current React page component
110
- You can try to find relative logs by using "x-request-id" header`;
111
- }
112
- }
113
- logMessage = `${logMessage}
114
- ${RootErrorBoundary !== null
115
- ? 'Root Error Boundary will be rendered for the client'
116
- : 'You can add Error Boundary for better UX - https://tramvai.dev/docs/features/error-boundaries'}'`;
117
- log[logLevel]({
118
- event: logEvent,
119
- message: logMessage,
120
- error,
121
- requestInfo,
122
- });
123
- const afterErrorResult = await runHandlers(afterError);
124
- if (!isNil__default["default"](afterErrorResult)) {
125
- return afterErrorResult;
126
- }
127
- reply.status(httpStatus);
128
- if (RootErrorBoundary !== null) {
129
- try {
130
- const stats = await fetchWebpackStats();
131
- const extractor = new server.ChunkExtractor({ stats, entrypoints: ['rootErrorBoundary'] });
132
- const url$1 = url.parse(requestInfo.url);
133
- const serializedError = {
134
- status: httpStatus,
135
- message: error.message,
136
- stack: error.stack,
137
- };
138
- const body = server$1.renderToString(react.createElement(RootErrorBoundary, { error: serializedError, url: url$1 })).replace('</head>', [
139
- '<script>' +
140
- `window.serverUrl = ${safeStrings.safeStringify(url$1)};` +
141
- `window.serverError = new Error(${safeStrings.safeStringify(serializedError.message)});` +
142
- `Object.assign(window.serverError, ${safeStrings.safeStringify(serializedError)});` +
143
- '</script>',
144
- extractor.getStyleTags(),
145
- extractor.getScriptTags(),
146
- '</head>',
147
- ]
148
- .filter(Boolean)
149
- .join('\n'));
150
- log.info({
151
- event: 'render-root-error-boundary',
152
- message: 'Render Root Error Boundary for the client',
153
- });
154
- reply.header('Content-Type', 'text/html; charset=utf-8');
155
- reply.header('Content-Length', Buffer.byteLength(body, 'utf8'));
156
- reply.header('Cache-Control', 'no-store, no-cache, must-revalidate');
157
- return body;
158
- }
159
- catch (e) {
160
- log.warn({
161
- event: 'failed-root-error-boundary',
162
- message: 'Root Error Boundary rendering failed',
163
- error: e,
164
- });
165
- }
166
- }
167
- throw error;
168
- });
169
- };
170
-
171
- exports.errorHandler = errorHandler;