@contrast/core 1.31.1 → 1.31.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.
@@ -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
- core.captureStacktrace = function(...args) {
32
- return stacktraceFactory.captureStacktrace(...args);
34
+ /** @type {StacktraceFactory['captureStacktrace']} */
35
+ core.captureStacktrace = function (obj, opts, key) {
36
+ return stacktraceFactory.captureStacktrace(obj, opts, key);
33
37
  };
34
38
 
35
- core.createSnapshot = function(...args) {
36
- return stacktraceFactory.createSnapshot(...args);
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 {object} params
79
- * @param {function} params.limitFn The constructorOpt param used when creating stack
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 ret;
110
+ let frames;
89
111
 
90
112
  /**
91
113
  * Generates array of frames from `target`'s `stack` getter
92
- * @returns {array}
114
+ * @returns {Frame[]}
93
115
  */
94
- function snapshot() {
95
- if (!ret) {
96
- const callsites = StacktraceFactory.generateCallsites(target);
97
- /* eslint-disable complexity */
98
- ret = (callsites || []).reduce((acc, callsite) => {
99
- if (StacktraceFactory.isCallsiteValid(callsite)) {
100
- const frame = StacktraceFactory.makeFrame(callsite);
101
-
102
- if (
103
- frame.file &&
104
- !`${frame.type}${frame.method}`.includes('ContrastMethods')
105
- ) {
106
- if (!isAgentPath(frame.file)) {
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
- return acc;
113
- }, []);
133
+ return acc;
134
+ },
135
+ [],
136
+ );
114
137
  }
115
138
 
116
- if (!prependFrames) return ret;
139
+ if (!prependFrames) return frames;
117
140
 
118
141
  return [
119
142
  ...prependFrames.map((f) => (typeof f == 'function' ? fnInspect.funcInfo(f) : f)),
120
- ...ret
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 {} error
130
- * @param {} limitFn
152
+ * @param {any} obj
153
+ * @param {Function=} constructorOpt
131
154
  */
132
- appendStackGetter(error, constructorOpt) {
155
+ appendStackGetter(obj, constructorOpt) {
133
156
  const { stackTraceLimit } = Error;
134
157
  Error.stackTraceLimit = this.stackTraceLimit;
135
- Error.captureStackTrace(error, constructorOpt);
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
- let evalOrigin, file, lineNumber, method, type;
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
- [, file, lineNumber] = evalOrigin.match(EVAL_ORIGIN_REGEX) || evalOrigin.endsWith('.ejs');
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 || callsite.getFileName();
157
- lineNumber = lineNumber || callsite.getLineNumber();
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 {object} error object with a `stack` getter property
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(...args: any[]): any;
25
- captureSnapshot(...args: any[]): any;
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
 
@@ -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.31.1",
3
+ "version": "1.31.2",
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)",