@eggjs/onerror 3.0.0 → 3.0.1-beta.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 (57) hide show
  1. package/README.md +2 -6
  2. package/dist/agent.d.ts +10 -0
  3. package/dist/agent.js +15 -0
  4. package/dist/app.d.ts +16 -0
  5. package/dist/app.js +111 -0
  6. package/{src/config/config.default.ts → dist/config/config.default.d.ts} +8 -12
  7. package/dist/config/config.default.js +11 -0
  8. package/dist/index.d.ts +1 -0
  9. package/dist/index.js +1 -0
  10. package/dist/lib/error_view.d.ts +158 -0
  11. package/dist/lib/error_view.js +224 -0
  12. package/dist/{commonjs/lib → lib}/onerror_page.mustache.html +1 -1
  13. package/dist/lib/utils.d.ts +10 -0
  14. package/dist/lib/utils.js +21 -0
  15. package/dist/types.d.ts +10 -0
  16. package/dist/types.js +1 -0
  17. package/package.json +33 -55
  18. package/dist/commonjs/agent.d.ts +0 -6
  19. package/dist/commonjs/agent.js +0 -16
  20. package/dist/commonjs/app.d.ts +0 -12
  21. package/dist/commonjs/app.js +0 -150
  22. package/dist/commonjs/config/config.default.d.ts +0 -27
  23. package/dist/commonjs/config/config.default.js +0 -15
  24. package/dist/commonjs/index.d.ts +0 -1
  25. package/dist/commonjs/index.js +0 -4
  26. package/dist/commonjs/lib/error_view.d.ts +0 -154
  27. package/dist/commonjs/lib/error_view.js +0 -248
  28. package/dist/commonjs/lib/utils.d.ts +0 -10
  29. package/dist/commonjs/lib/utils.js +0 -53
  30. package/dist/commonjs/package.json +0 -3
  31. package/dist/commonjs/types.d.ts +0 -7
  32. package/dist/commonjs/types.js +0 -3
  33. package/dist/esm/agent.d.ts +0 -6
  34. package/dist/esm/agent.js +0 -13
  35. package/dist/esm/app.d.ts +0 -12
  36. package/dist/esm/app.js +0 -144
  37. package/dist/esm/config/config.default.d.ts +0 -27
  38. package/dist/esm/config/config.default.js +0 -10
  39. package/dist/esm/index.d.ts +0 -1
  40. package/dist/esm/index.js +0 -2
  41. package/dist/esm/lib/error_view.d.ts +0 -154
  42. package/dist/esm/lib/error_view.js +0 -241
  43. package/dist/esm/lib/onerror_page.mustache.html +0 -761
  44. package/dist/esm/lib/utils.d.ts +0 -10
  45. package/dist/esm/lib/utils.js +0 -43
  46. package/dist/esm/package.json +0 -3
  47. package/dist/esm/types.d.ts +0 -7
  48. package/dist/esm/types.js +0 -2
  49. package/dist/package.json +0 -4
  50. package/src/agent.ts +0 -12
  51. package/src/app.ts +0 -160
  52. package/src/index.ts +0 -1
  53. package/src/lib/error_view.ts +0 -281
  54. package/src/lib/onerror_page.mustache.html +0 -761
  55. package/src/lib/utils.ts +0 -47
  56. package/src/types.ts +0 -12
  57. package/src/typings/index.d.ts +0 -4
@@ -1,10 +0,0 @@
1
- import type { Context, EggCore } from '@eggjs/core';
2
- import type { OnerrorError } from 'koa-onerror';
3
- export declare function detectErrorMessage(ctx: Context, err: OnerrorError): string;
4
- export declare function detectStatus(err: OnerrorError): number;
5
- export declare function accepts(ctx: Context): "json" | "js" | "html";
6
- export declare function isProd(app: EggCore): boolean;
7
- /**
8
- * Get the source directory name
9
- */
10
- export declare function getSourceDirname(): string;
@@ -1,43 +0,0 @@
1
- import path from 'node:path';
2
- import { fileURLToPath } from 'node:url';
3
- export function detectErrorMessage(ctx, err) {
4
- // detect json parse error
5
- if (err.status === 400 &&
6
- err.name === 'SyntaxError' &&
7
- ctx.request.is('application/json', 'application/vnd.api+json', 'application/csp-report')) {
8
- return 'Problems parsing JSON';
9
- }
10
- return err.message;
11
- }
12
- export function detectStatus(err) {
13
- // detect status
14
- let status = err.status || 500;
15
- if (status < 200) {
16
- // invalid status consider as 500, like urllib will return -1 status
17
- status = 500;
18
- }
19
- return status;
20
- }
21
- export function accepts(ctx) {
22
- if (ctx.acceptJSON)
23
- return 'json';
24
- if (ctx.acceptJSONP)
25
- return 'js';
26
- return 'html';
27
- }
28
- export function isProd(app) {
29
- return app.config.env !== 'local' && app.config.env !== 'unittest';
30
- }
31
- /**
32
- * Get the source directory name
33
- */
34
- export function getSourceDirname() {
35
- if (typeof __dirname === 'string') {
36
- return path.dirname(__dirname);
37
- }
38
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
39
- // @ts-ignore
40
- const __filename = fileURLToPath(import.meta.url);
41
- return path.dirname(path.dirname(__filename));
42
- }
43
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvbGliL3V0aWxzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sSUFBSSxNQUFNLFdBQVcsQ0FBQztBQUM3QixPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sVUFBVSxDQUFDO0FBSXpDLE1BQU0sVUFBVSxrQkFBa0IsQ0FBQyxHQUFZLEVBQUUsR0FBaUI7SUFDaEUsMEJBQTBCO0lBQzFCLElBQUksR0FBRyxDQUFDLE1BQU0sS0FBSyxHQUFHO1FBQ2xCLEdBQUcsQ0FBQyxJQUFJLEtBQUssYUFBYTtRQUMxQixHQUFHLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQyxrQkFBa0IsRUFBRSwwQkFBMEIsRUFBRSx3QkFBd0IsQ0FBQyxFQUFFLENBQUM7UUFDN0YsT0FBTyx1QkFBdUIsQ0FBQztJQUNqQyxDQUFDO0lBQ0QsT0FBTyxHQUFHLENBQUMsT0FBTyxDQUFDO0FBQ3JCLENBQUM7QUFFRCxNQUFNLFVBQVUsWUFBWSxDQUFDLEdBQWlCO0lBQzVDLGdCQUFnQjtJQUNoQixJQUFJLE1BQU0sR0FBRyxHQUFHLENBQUMsTUFBTSxJQUFJLEdBQUcsQ0FBQztJQUMvQixJQUFJLE1BQU0sR0FBRyxHQUFHLEVBQUUsQ0FBQztRQUNqQixvRUFBb0U7UUFDcEUsTUFBTSxHQUFHLEdBQUcsQ0FBQztJQUNmLENBQUM7SUFDRCxPQUFPLE1BQU0sQ0FBQztBQUNoQixDQUFDO0FBRUQsTUFBTSxVQUFVLE9BQU8sQ0FBQyxHQUFZO0lBQ2xDLElBQUksR0FBRyxDQUFDLFVBQVU7UUFBRSxPQUFPLE1BQU0sQ0FBQztJQUNsQyxJQUFJLEdBQUcsQ0FBQyxXQUFXO1FBQUUsT0FBTyxJQUFJLENBQUM7SUFDakMsT0FBTyxNQUFNLENBQUM7QUFDaEIsQ0FBQztBQUVELE1BQU0sVUFBVSxNQUFNLENBQUMsR0FBWTtJQUNqQyxPQUFPLEdBQUcsQ0FBQyxNQUFNLENBQUMsR0FBRyxLQUFLLE9BQU8sSUFBSSxHQUFHLENBQUMsTUFBTSxDQUFDLEdBQUcsS0FBSyxVQUFVLENBQUM7QUFDckUsQ0FBQztBQUVEOztHQUVHO0FBQ0gsTUFBTSxVQUFVLGdCQUFnQjtJQUM5QixJQUFJLE9BQU8sU0FBUyxLQUFLLFFBQVEsRUFBRSxDQUFDO1FBQ2xDLE9BQU8sSUFBSSxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUMsQ0FBQztJQUNqQyxDQUFDO0lBQ0QsNkRBQTZEO0lBQzdELGFBQWE7SUFDYixNQUFNLFVBQVUsR0FBRyxhQUFhLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUNsRCxPQUFPLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDO0FBQ2hELENBQUMifQ==
@@ -1,3 +0,0 @@
1
- {
2
- "type": "module"
3
- }
@@ -1,7 +0,0 @@
1
- import type { OnerrorConfig } from './config/config.default.js';
2
- export type { OnerrorConfig };
3
- declare module '@eggjs/core' {
4
- interface EggAppConfig {
5
- onerror: OnerrorConfig;
6
- }
7
- }
package/dist/esm/types.js DELETED
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHlwZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdHlwZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiJ9
package/dist/package.json DELETED
@@ -1,4 +0,0 @@
1
- {
2
- "name": "@eggjs/onerror",
3
- "version": "3.0.0"
4
- }
package/src/agent.ts DELETED
@@ -1,12 +0,0 @@
1
- import type { ILifecycleBoot, EggCore } from '@eggjs/core';
2
-
3
- export default class Boot implements ILifecycleBoot {
4
- constructor(private agent: EggCore) {}
5
-
6
- async didLoad() {
7
- // should watch error event
8
- this.agent.on('error', err => {
9
- this.agent.coreLogger.error(err);
10
- });
11
- }
12
- }
package/src/app.ts DELETED
@@ -1,160 +0,0 @@
1
- import http from 'node:http';
2
- import fs from 'node:fs';
3
- import { onerror, type OnerrorOptions, type OnerrorError } from 'koa-onerror';
4
- import type { ILifecycleBoot, EggCore, Context } from '@eggjs/core';
5
- import { ErrorView } from './lib/error_view.js';
6
- import { isProd, detectStatus, detectErrorMessage, accepts } from './lib/utils.js';
7
- import type { OnerrorConfig } from './config/config.default.js';
8
-
9
- export interface OnerrorErrorWithCode extends OnerrorError {
10
- code?: string;
11
- type?: string;
12
- errors?: any[];
13
- }
14
-
15
- export default class Boot implements ILifecycleBoot {
16
- constructor(private app: EggCore) {}
17
-
18
- async didLoad() {
19
- // logging error
20
- const config = this.app.config.onerror;
21
- const viewTemplate = fs.readFileSync(config.templatePath, 'utf8');
22
- const app = this.app;
23
- app.on('error', (err, ctx) => {
24
- if (!ctx) {
25
- ctx = app.currentContext || app.createAnonymousContext();
26
- }
27
- if (config.appErrorFilter && !config.appErrorFilter(err, ctx)) return;
28
-
29
- const status = detectStatus(err);
30
- // 5xx
31
- if (status >= 500) {
32
- try {
33
- ctx.logger.error(err);
34
- } catch (ex) {
35
- app.logger.error(err);
36
- app.logger.error(ex);
37
- }
38
- return;
39
- }
40
-
41
- // 4xx
42
- try {
43
- ctx.logger.warn(err);
44
- } catch (ex) {
45
- app.logger.warn(err);
46
- app.logger.error(ex);
47
- }
48
- });
49
-
50
- const errorOptions: OnerrorOptions = {
51
- // support customize accepts function
52
- accepts(this: Context) {
53
- const fn = config.accepts || accepts;
54
- return fn(this as any);
55
- },
56
-
57
- html(err, ctx: Context) {
58
- const status = detectStatus(err);
59
- const errorPageUrl = typeof config.errorPageUrl === 'function'
60
- ? config.errorPageUrl(err, ctx)
61
- : config.errorPageUrl;
62
-
63
- // keep the real response status
64
- ctx.realStatus = status;
65
- // don't respond any error message in production env
66
- if (isProd(app)) {
67
- // 5xx
68
- if (status >= 500) {
69
- if (errorPageUrl) {
70
- const statusQuery =
71
- (errorPageUrl.indexOf('?') > 0 ? '&' : '?') +
72
- `real_status=${status}`;
73
- return ctx.redirect(errorPageUrl + statusQuery);
74
- }
75
- ctx.status = 500;
76
- ctx.body = `<h2>Internal Server Error, real status: ${status}</h2>`;
77
- return;
78
- }
79
- // 4xx
80
- ctx.status = status;
81
- ctx.body = `<h2>${status} ${http.STATUS_CODES[status]}</h2>`;
82
- return;
83
- }
84
- // show simple error format for unittest
85
- if (app.config.env === 'unittest') {
86
- ctx.status = status;
87
- ctx.body = `${err.name}: ${err.message}\n${err.stack}`;
88
- return;
89
- }
90
-
91
- const errorView = new ErrorView(ctx, err, viewTemplate);
92
- ctx.body = errorView.toHTML();
93
- },
94
-
95
- json(err: OnerrorErrorWithCode, ctx: Context) {
96
- const status = detectStatus(err);
97
- let errorJson: Record<string, any> = {};
98
-
99
- ctx.status = status;
100
- const code = err.code ?? err.type;
101
- const message = detectErrorMessage(ctx, err);
102
-
103
- if (isProd(app)) {
104
- // 5xx server side error
105
- if (status >= 500) {
106
- errorJson = {
107
- code,
108
- // don't respond any error message in production env
109
- message: http.STATUS_CODES[status],
110
- };
111
- } else {
112
- // 4xx client side error
113
- // addition `errors`
114
- errorJson = {
115
- code,
116
- message,
117
- errors: err.errors,
118
- };
119
- }
120
- } else {
121
- errorJson = {
122
- code,
123
- message,
124
- errors: err.errors,
125
- };
126
-
127
- if (status >= 500) {
128
- // provide detail error stack in local env
129
- errorJson.stack = err.stack;
130
- errorJson.name = err.name;
131
- for (const key in err) {
132
- if (!errorJson[key]) {
133
- errorJson[key] = (err as any)[key];
134
- }
135
- }
136
- }
137
- }
138
-
139
- ctx.body = errorJson;
140
- },
141
-
142
- js(err, ctx: Context) {
143
- errorOptions.json!.call(ctx, err, ctx);
144
-
145
- if (ctx.createJsonpBody) {
146
- ctx.createJsonpBody(ctx.body);
147
- }
148
- },
149
- };
150
-
151
- // support customize error response
152
- const keys: (keyof OnerrorConfig)[] = [ 'all', 'html', 'json', 'text', 'js' ];
153
- for (const type of keys) {
154
- if (config[type]) {
155
- Reflect.set(errorOptions, type, config[type]);
156
- }
157
- }
158
- onerror(app, errorOptions);
159
- }
160
- }
package/src/index.ts DELETED
@@ -1 +0,0 @@
1
- import './types.js';
@@ -1,281 +0,0 @@
1
- // modify from https://github.com/poppinss/youch/blob/develop/src/Youch/index.js
2
-
3
- import fs from 'node:fs';
4
- import path from 'node:path';
5
- import util from 'node:util';
6
- import { parse } from 'cookie';
7
- import Mustache from 'mustache';
8
- import stackTrace, { type StackFrame } from 'stack-trace';
9
- import { detectErrorMessage } from './utils.js';
10
- import type { OnerrorError } from 'koa-onerror';
11
- import type { Context } from '@eggjs/core';
12
-
13
- const startingSlashRegex = /\\|\//;
14
-
15
- export interface FrameSource {
16
- pre: string[];
17
- line: string;
18
- post: string[];
19
- }
20
-
21
- export interface Frame extends StackFrame {
22
- context?: FrameSource;
23
- }
24
-
25
- export class ErrorView {
26
- ctx: Context;
27
- error: OnerrorError;
28
- request: Context['request'];
29
- app: Context['app'];
30
- assets: Map<string, string>;
31
- viewTemplate: string;
32
-
33
- codeContext = 5;
34
- _filterHeaders = [ 'cookie', 'connection' ];
35
-
36
- constructor(ctx: Context, error: OnerrorError, template: string) {
37
- this.ctx = ctx;
38
- this.error = error;
39
- this.request = ctx.request;
40
- this.app = ctx.app;
41
- this.assets = new Map();
42
- this.viewTemplate = template;
43
- }
44
-
45
- /**
46
- * get html error page
47
- *
48
- * @return {String} html page
49
- */
50
- toHTML(): string {
51
- const stack = this.parseError();
52
- const data = this.serializeData(stack, (frame, index) => {
53
- const serializedFrame = this.serializeFrame(frame);
54
- serializedFrame.classes = this.getFrameClasses(frame, index);
55
- return serializedFrame;
56
- });
57
-
58
- return this.compileView(this.viewTemplate, {
59
- ...data,
60
- appInfo: this.serializeAppInfo(),
61
- request: this.serializeRequest(),
62
- });
63
- }
64
-
65
- /**
66
- * compile view
67
- *
68
- * @param {String} tpl - template
69
- * @param {Object} locals - data used by template
70
- */
71
- compileView(tpl: string, locals: Record<string, unknown>) {
72
- return Mustache.render(tpl, locals);
73
- }
74
-
75
- /**
76
- * check if the frame is node native file.
77
- *
78
- * @param {Frame} frame - current frame
79
- */
80
- isNode(frame: Frame) {
81
- if (frame.isNative()) {
82
- return true;
83
- }
84
- const filename = frame.getFileName() || '';
85
- return !path.isAbsolute(filename) && filename[0] !== '.';
86
- }
87
-
88
- /**
89
- * check if the frame is app modules.
90
- *
91
- * @param {Object} frame - current frame
92
- */
93
- isApp(frame: Frame) {
94
- if (this.isNode(frame)) {
95
- return false;
96
- }
97
- const filename = frame.getFileName() || '';
98
- return !filename.includes('node_modules' + path.sep);
99
- }
100
-
101
- /**
102
- * cache file asserts
103
- *
104
- * @param {String} key - assert key
105
- * @param {String} value - assert content
106
- */
107
- setAssets(key: string, value: string) {
108
- this.assets.set(key, value);
109
- }
110
-
111
- /**
112
- * get cache file asserts
113
- *
114
- * @param {String} key - assert key
115
- */
116
- getAssets(key: string) {
117
- return this.assets.get(key);
118
- }
119
-
120
- /**
121
- * get frame source
122
- *
123
- * @param {Object} frame - current frame
124
- */
125
- getFrameSource(frame: StackFrame): FrameSource {
126
- const filename = frame.getFileName();
127
- const lineNumber = frame.getLineNumber();
128
- let contents = this.getAssets(filename);
129
- if (!contents) {
130
- contents = fs.existsSync(filename) ? fs.readFileSync(filename, 'utf8') : '';
131
- this.setAssets(filename, contents);
132
- }
133
- const lines = contents.split(/\r?\n/);
134
-
135
- return {
136
- pre: lines.slice(Math.max(0, lineNumber - (this.codeContext + 1)), lineNumber - 1),
137
- line: lines[lineNumber - 1],
138
- post: lines.slice(lineNumber, lineNumber + this.codeContext),
139
- };
140
- }
141
-
142
- /**
143
- * parse error and return frame stack
144
- */
145
- parseError() {
146
- const stack = stackTrace.parse(this.error);
147
- return stack.map((frame: Frame) => {
148
- if (!this.isNode(frame)) {
149
- frame.context = this.getFrameSource(frame);
150
- }
151
- return frame;
152
- });
153
- }
154
-
155
- /**
156
- * get stack context
157
- *
158
- * @param {Object} frame - current frame
159
- */
160
- getContext(frame: Frame) {
161
- if (!frame.context) {
162
- return {};
163
- }
164
-
165
- return {
166
- start: frame.getLineNumber() - (frame.context.pre || []).length,
167
- pre: frame.context.pre.join('\n'),
168
- line: frame.context.line,
169
- post: frame.context.post.join('\n'),
170
- };
171
- }
172
-
173
- /**
174
- * get frame classes, let view identify the frame
175
- *
176
- * @param {any} frame - current frame
177
- * @param {any} index - current index
178
- */
179
- getFrameClasses(frame: Frame, index: number) {
180
- const classes: string[] = [];
181
- if (index === 0) {
182
- classes.push('active');
183
- }
184
-
185
- if (!this.isApp(frame)) {
186
- classes.push('native-frame');
187
- }
188
-
189
- return classes.join(' ');
190
- }
191
-
192
- /**
193
- * serialize frame and return meaningful data
194
- *
195
- * @param {Object} frame - current frame
196
- */
197
- serializeFrame(frame: Frame) {
198
- const filename = frame.getFileName();
199
- const relativeFileName = filename.includes(process.cwd())
200
- ? filename.replace(process.cwd(), '').replace(startingSlashRegex, '')
201
- : filename;
202
- const extname = path.extname(filename).replace('.', '');
203
-
204
- return {
205
- extname,
206
- file: relativeFileName,
207
- method: frame.getFunctionName(),
208
- line: frame.getLineNumber(),
209
- column: frame.getColumnNumber(),
210
- context: this.getContext(frame),
211
- classes: '',
212
- };
213
- }
214
-
215
- /**
216
- * serialize base data
217
- *
218
- * @param {Object} stack - frame stack
219
- * @param {Function} frameFormatter - frame formatter function
220
- */
221
- serializeData(stack: Frame[], frameFormatter: (frame: Frame, index: number) => any) {
222
- const code = Reflect.get(this.error, 'code') ?? Reflect.get(this.error, 'type');
223
- let message = detectErrorMessage(this.ctx, this.error);
224
- if (code) {
225
- message = `${message} (code: ${code})`;
226
- }
227
- return {
228
- code,
229
- message,
230
- name: this.error.name,
231
- status: this.error.status,
232
- frames: stack instanceof Array ? stack.filter(frame => frame.getFileName()).map(frameFormatter) : [],
233
- };
234
- }
235
-
236
- /**
237
- * serialize request object
238
- */
239
- serializeRequest() {
240
- const headers: { key: string; value: string | string[] | undefined }[] = [];
241
-
242
- Object.keys(this.request.headers).forEach(key => {
243
- if (this._filterHeaders.includes(key)) {
244
- return;
245
- }
246
- headers.push({
247
- key,
248
- value: this.request.headers[key],
249
- });
250
- });
251
-
252
- const parsedCookies = parse(this.request.headers.cookie || '');
253
- const cookies = Object.keys(parsedCookies).map(key => {
254
- return { key, value: parsedCookies[key] };
255
- });
256
-
257
- return {
258
- url: this.request.url,
259
- httpVersion: this.request.req.httpVersion,
260
- method: this.request.method,
261
- connection: this.request.headers.connection,
262
- headers,
263
- cookies,
264
- };
265
- }
266
-
267
- /**
268
- * serialize app info object
269
- */
270
- serializeAppInfo() {
271
- let config = this.app.config;
272
- if ('dumpConfigToObject' in this.app && typeof this.app.dumpConfigToObject === 'function') {
273
- config = this.app.dumpConfigToObject().config.config;
274
- }
275
- return {
276
- baseDir: this.app.config.baseDir as string,
277
- config: util.inspect(config),
278
- };
279
- }
280
- }
281
-