@contrast/core 1.31.1 → 1.32.0
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/lib/capture-stacktrace.js +94 -51
- package/lib/index.d.ts +16 -2
- package/lib/is-agent-path.js +3 -3
- package/package.json +2 -2
|
@@ -12,12 +12,15 @@
|
|
|
12
12
|
* engineered, modified, repackaged, sold, redistributed or otherwise used in a
|
|
13
13
|
* way not consistent with the End User License Agreement.
|
|
14
14
|
*/
|
|
15
|
-
|
|
15
|
+
// @ts-check
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
18
|
const process = require('process');
|
|
19
19
|
const fnInspect = require('@contrast/fn-inspect');
|
|
20
20
|
|
|
21
|
+
/** @typedef {import('.').Frame} Frame */
|
|
22
|
+
/** @typedef {import('.').CreateSnapshotOpts} CreateSnapshotOpts */
|
|
23
|
+
|
|
21
24
|
const EVAL_ORIGIN_REGEX = /\((.*?):(\d+):\d+\)/;
|
|
22
25
|
|
|
23
26
|
module.exports = function(core) {
|
|
@@ -28,28 +31,38 @@ module.exports = function(core) {
|
|
|
28
31
|
isAgentPath
|
|
29
32
|
});
|
|
30
33
|
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
/** @type {StacktraceFactory['captureStacktrace']} */
|
|
35
|
+
core.captureStacktrace = function (obj, opts, key) {
|
|
36
|
+
return stacktraceFactory.captureStacktrace(obj, opts, key);
|
|
33
37
|
};
|
|
34
38
|
|
|
35
|
-
|
|
36
|
-
|
|
39
|
+
/** @type {StacktraceFactory['createSnapshot']} */
|
|
40
|
+
core.createSnapshot = function (opts) {
|
|
41
|
+
return stacktraceFactory.createSnapshot(opts);
|
|
37
42
|
};
|
|
38
43
|
|
|
39
44
|
return core.captureStacktrace;
|
|
40
45
|
};
|
|
41
46
|
|
|
42
|
-
/**
|
|
43
|
-
* The factory will set stacktrace limit for stackframe lists created by it.
|
|
44
|
-
* @param {number} stackTraceLimit set the stack trace limit
|
|
45
|
-
* @param {function} isAgentPath function indicating if path is agent code
|
|
46
|
-
*/
|
|
47
47
|
class StacktraceFactory {
|
|
48
|
+
/**
|
|
49
|
+
* The factory will set stacktrace limit for stackframe lists created by it.
|
|
50
|
+
* @param {Object} opts
|
|
51
|
+
* @param {number} opts.stackTraceLimit set the stack trace limit
|
|
52
|
+
* @param {(path: string) => boolean} opts.isAgentPath function indicating if path is agent code
|
|
53
|
+
*/
|
|
48
54
|
constructor({ stackTraceLimit, isAgentPath }) {
|
|
49
55
|
this.stackTraceLimit = stackTraceLimit;
|
|
50
56
|
this.isAgentPath = isAgentPath;
|
|
51
57
|
}
|
|
52
58
|
|
|
59
|
+
/**
|
|
60
|
+
* @template T
|
|
61
|
+
* @param {T} obj
|
|
62
|
+
* @param {CreateSnapshotOpts} opts
|
|
63
|
+
* @param {string} key
|
|
64
|
+
* @returns {T}
|
|
65
|
+
*/
|
|
53
66
|
captureStacktrace(obj, opts, key = 'stack') {
|
|
54
67
|
let stack;
|
|
55
68
|
const snapshot = this.createSnapshot(opts);
|
|
@@ -71,91 +84,117 @@ class StacktraceFactory {
|
|
|
71
84
|
return obj;
|
|
72
85
|
}
|
|
73
86
|
|
|
87
|
+
/**
|
|
88
|
+
* @param {Frame} frame
|
|
89
|
+
* @returns {boolean}
|
|
90
|
+
*/
|
|
91
|
+
shouldAppendFrame(frame) {
|
|
92
|
+
return (
|
|
93
|
+
!!frame.file &&
|
|
94
|
+
!this.isAgentPath(frame.file) &&
|
|
95
|
+
!`${frame.type}${frame.method}`.includes('ContrastMethods')
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
74
99
|
/**
|
|
75
100
|
* Creates a function that will build a stacktrace when invoked. It will keep
|
|
76
101
|
* an error in a closure whose stack will be generated and processed when the
|
|
77
102
|
* returned function executes. The result will be cached.
|
|
78
|
-
* @param {
|
|
79
|
-
* @
|
|
80
|
-
* @returns {function}
|
|
103
|
+
* @param {CreateSnapshotOpts} params
|
|
104
|
+
* @returns {() => Frame[]}
|
|
81
105
|
*/
|
|
82
106
|
createSnapshot({ constructorOpt, prependFrames } = {}) {
|
|
83
|
-
const { isAgentPath } = this;
|
|
84
107
|
const target = {};
|
|
85
|
-
|
|
86
108
|
this.appendStackGetter(target, constructorOpt);
|
|
87
109
|
|
|
88
|
-
let
|
|
110
|
+
let frames;
|
|
89
111
|
|
|
90
112
|
/**
|
|
91
113
|
* Generates array of frames from `target`'s `stack` getter
|
|
92
|
-
* @returns {
|
|
114
|
+
* @returns {Frame[]}
|
|
93
115
|
*/
|
|
94
|
-
|
|
95
|
-
if (!
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if (
|
|
116
|
+
const snapshot = () => {
|
|
117
|
+
if (!frames) {
|
|
118
|
+
// @ts-expect-error target has had a stack getter appended above.
|
|
119
|
+
const callsites = StacktraceFactory.generateCallsites(target) ?? [];
|
|
120
|
+
frames = callsites.reduce(
|
|
121
|
+
/**
|
|
122
|
+
* @param {Frame[]} acc
|
|
123
|
+
* @param {NodeJS.CallSite} callsite
|
|
124
|
+
*/
|
|
125
|
+
(acc, callsite) => {
|
|
126
|
+
if (StacktraceFactory.isCallsiteValid(callsite)) {
|
|
127
|
+
const frame = StacktraceFactory.makeFrame(callsite);
|
|
128
|
+
if (this.shouldAppendFrame(frame)) {
|
|
107
129
|
acc.push(frame);
|
|
108
130
|
}
|
|
109
131
|
}
|
|
110
|
-
}
|
|
111
132
|
|
|
112
|
-
|
|
113
|
-
|
|
133
|
+
return acc;
|
|
134
|
+
},
|
|
135
|
+
[],
|
|
136
|
+
);
|
|
114
137
|
}
|
|
115
138
|
|
|
116
|
-
if (!prependFrames) return
|
|
139
|
+
if (!prependFrames) return frames;
|
|
117
140
|
|
|
118
141
|
return [
|
|
119
142
|
...prependFrames.map((f) => (typeof f == 'function' ? fnInspect.funcInfo(f) : f)),
|
|
120
|
-
...
|
|
143
|
+
...frames,
|
|
121
144
|
];
|
|
122
|
-
}
|
|
145
|
+
};
|
|
123
146
|
|
|
124
147
|
return snapshot;
|
|
125
148
|
}
|
|
126
149
|
|
|
127
150
|
/**
|
|
128
151
|
* Based on stacktrace limit and constructor opt, will append a stack getter
|
|
129
|
-
* @param {}
|
|
130
|
-
* @param {}
|
|
152
|
+
* @param {any} obj
|
|
153
|
+
* @param {Function=} constructorOpt
|
|
131
154
|
*/
|
|
132
|
-
appendStackGetter(
|
|
155
|
+
appendStackGetter(obj, constructorOpt) {
|
|
133
156
|
const { stackTraceLimit } = Error;
|
|
134
157
|
Error.stackTraceLimit = this.stackTraceLimit;
|
|
135
|
-
Error.captureStackTrace(
|
|
158
|
+
Error.captureStackTrace(obj, constructorOpt);
|
|
136
159
|
Error.stackTraceLimit = stackTraceLimit;
|
|
137
160
|
}
|
|
138
161
|
|
|
162
|
+
/**
|
|
163
|
+
* @param {any} callsite
|
|
164
|
+
* @returns {boolean}
|
|
165
|
+
*/
|
|
139
166
|
static isCallsiteValid(callsite) {
|
|
140
167
|
return callsite instanceof Callsite;
|
|
141
168
|
}
|
|
142
169
|
|
|
143
170
|
/**
|
|
144
171
|
* Creates an array frame objects from an array of Callsite instances
|
|
145
|
-
* @param {} callsite
|
|
146
|
-
* @returns {}
|
|
172
|
+
* @param {NodeJS.CallSite} callsite
|
|
173
|
+
* @returns {Frame}
|
|
147
174
|
*/
|
|
148
175
|
static makeFrame(callsite) {
|
|
149
|
-
|
|
176
|
+
/** @type {string | undefined} */
|
|
177
|
+
let evalOrigin;
|
|
178
|
+
/** @type {string | undefined} */
|
|
179
|
+
let file;
|
|
180
|
+
/** @type {number | null} */
|
|
181
|
+
let lineNumber = null;
|
|
182
|
+
/** @type {string | null} */
|
|
183
|
+
let method = null;
|
|
184
|
+
/** @type {string | null} */
|
|
185
|
+
let type;
|
|
150
186
|
|
|
151
187
|
if (callsite.isEval()) {
|
|
152
188
|
evalOrigin = StacktraceFactory.formatFileName(callsite.getEvalOrigin());
|
|
153
|
-
|
|
189
|
+
const matches = EVAL_ORIGIN_REGEX.exec(evalOrigin);
|
|
190
|
+
if (matches) {
|
|
191
|
+
file = matches[1];
|
|
192
|
+
lineNumber = parseInt(matches[2]);
|
|
193
|
+
}
|
|
154
194
|
}
|
|
155
195
|
|
|
156
|
-
file = file
|
|
157
|
-
lineNumber = lineNumber
|
|
158
|
-
|
|
196
|
+
file = file ?? callsite.getFileName();
|
|
197
|
+
lineNumber = lineNumber ?? callsite.getLineNumber();
|
|
159
198
|
method = callsite.getFunctionName();
|
|
160
199
|
type = callsite.getTypeName();
|
|
161
200
|
|
|
@@ -167,6 +206,10 @@ class StacktraceFactory {
|
|
|
167
206
|
return { eval: evalOrigin, file, lineNumber, method, type };
|
|
168
207
|
}
|
|
169
208
|
|
|
209
|
+
/**
|
|
210
|
+
* @param {string} fileName
|
|
211
|
+
* @returns {string}
|
|
212
|
+
*/
|
|
170
213
|
static formatFileName(fileName = '') {
|
|
171
214
|
const cwd = `${process.cwd()}/`;
|
|
172
215
|
const idx = fileName.indexOf(cwd);
|
|
@@ -181,15 +224,15 @@ class StacktraceFactory {
|
|
|
181
224
|
* Will access `stack` getter propery on the provided error to generate
|
|
182
225
|
* a stacktrace. We capture the callsite instances passed to `prepareStacktrace`
|
|
183
226
|
* using an ephemeral monkey patch.
|
|
184
|
-
* @param {
|
|
185
|
-
* @returns {}
|
|
227
|
+
* @param {Error} error object with a `stack` getter property
|
|
228
|
+
* @returns {NodeJS.CallSite[]}
|
|
186
229
|
*/
|
|
187
230
|
static generateCallsites(error) {
|
|
188
|
-
let callsites;
|
|
231
|
+
let callsites = [];
|
|
189
232
|
|
|
190
233
|
const { prepareStackTrace } = Error;
|
|
191
234
|
|
|
192
|
-
Error.prepareStackTrace = function(_, _callsites) {
|
|
235
|
+
Error.prepareStackTrace = function contrastPrepareStackTrace(_, _callsites) {
|
|
193
236
|
callsites = _callsites;
|
|
194
237
|
return _callsites;
|
|
195
238
|
};
|
package/lib/index.d.ts
CHANGED
|
@@ -15,14 +15,28 @@
|
|
|
15
15
|
|
|
16
16
|
import { AppInfo, Messages } from '@contrast/common';
|
|
17
17
|
|
|
18
|
+
interface Frame {
|
|
19
|
+
eval: string | undefined;
|
|
20
|
+
file: string | undefined;
|
|
21
|
+
lineNumber: number | null;
|
|
22
|
+
method: string | null;
|
|
23
|
+
type: string | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/* eslint-disable @typescript-eslint/ban-types */
|
|
27
|
+
interface CreateSnapshotOpts {
|
|
28
|
+
constructorOpt?: Function;
|
|
29
|
+
prependFrames?: (Function | Frame)[];
|
|
30
|
+
}
|
|
31
|
+
|
|
18
32
|
export interface Core {
|
|
19
33
|
agentName: string;
|
|
20
34
|
agentVersion: string;
|
|
21
35
|
|
|
22
36
|
appInfo: AppInfo;
|
|
23
37
|
|
|
24
|
-
captureStackTrace(
|
|
25
|
-
|
|
38
|
+
captureStackTrace<T>(obj: T, opts: CreateSnapshotOpts, key: string): T;
|
|
39
|
+
createSnapshot(opts?: CreateSnapshotOpts): () => Frame[];
|
|
26
40
|
|
|
27
41
|
isAgentPath(path: string): boolean;
|
|
28
42
|
|
package/lib/is-agent-path.js
CHANGED
|
@@ -12,15 +12,15 @@
|
|
|
12
12
|
* engineered, modified, repackaged, sold, redistributed or otherwise used in a
|
|
13
13
|
* way not consistent with the End User License Agreement.
|
|
14
14
|
*/
|
|
15
|
-
|
|
16
15
|
'use strict';
|
|
17
16
|
|
|
18
|
-
/**
|
|
19
|
-
*/
|
|
20
17
|
module.exports = function(core) {
|
|
21
18
|
const { config } = core;
|
|
22
19
|
|
|
23
20
|
/**
|
|
21
|
+
* Returns true if the provided path matches any of the configured filters.
|
|
22
|
+
* @param {string} path
|
|
23
|
+
* @returns {boolean}
|
|
24
24
|
*/
|
|
25
25
|
function isAgentPath(path) {
|
|
26
26
|
for (const seg of config.agent.stack_trace_filters) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.32.0",
|
|
4
4
|
"description": "Preconfigured Contrast agent core services and models",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": "Contrast Security <nodejs@contrastsecurity.com> (https://www.contrastsecurity.com)",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"test": "../scripts/test.sh"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@contrast/common": "1.
|
|
19
|
+
"@contrast/common": "1.21.0",
|
|
20
20
|
"@contrast/find-package-json": "^1.0.0",
|
|
21
21
|
"@contrast/fn-inspect": "^4.0.0"
|
|
22
22
|
}
|