@sentry/nextjs
Version:
Official Sentry SDK for Next.js
170 lines (144 loc) • 6.04 kB
JavaScript
var {
_nullishCoalesce,
_optionalChain
} = require('@sentry/utils');
Object.defineProperty(exports, '__esModule', { value: true });
const utils = require('@sentry/utils');
const stackTraceParser = require('stacktrace-parser');
const globalWithInjectedValues = utils.GLOBAL_OBJ
;
async function resolveStackFrame(
frame,
error,
) {
try {
if (!(_optionalChain([frame, 'access', _ => _.file, 'optionalAccess', _2 => _2.startsWith, 'call', _3 => _3('webpack-internal:')]) || _optionalChain([frame, 'access', _4 => _4.file, 'optionalAccess', _5 => _5.startsWith, 'call', _6 => _6('file:')]))) {
return null;
}
const params = new URLSearchParams();
params.append('isServer', String(false)); // doesn't matter since it is overwritten by isAppDirectory
params.append('isEdgeServer', String(false)); // doesn't matter since it is overwritten by isAppDirectory
params.append('isAppDirectory', String(true)); // will force server to do more thorough checking
params.append('errorMessage', error.toString());
Object.keys(frame).forEach(key => {
params.append(key, (_nullishCoalesce(frame[key ], () => ( ''))).toString());
});
let basePath = _nullishCoalesce(globalWithInjectedValues.__sentryBasePath, () => ( ''));
// Prefix the basepath with a slash if it doesn't have one
if (basePath !== '' && !basePath.match(/^\//)) {
basePath = `/${basePath}`;
}
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 3000);
const res = await fetch(
`${
// eslint-disable-next-line no-restricted-globals
typeof window === 'undefined' ? 'http://localhost:3000' : '' // TODO: handle the case where users define a different port
}${basePath}/__nextjs_original-stack-frame?${params.toString()}`,
{
signal: controller.signal,
},
).finally(() => {
clearTimeout(timer);
});
if (!res.ok || res.status === 204) {
return null;
}
const body = await res.json();
return {
originalCodeFrame: body.originalCodeFrame,
originalStackFrame: body.originalStackFrame,
};
} catch (e) {
return null;
}
}
function parseOriginalCodeFrame(codeFrame)
{
const preProcessedLines = codeFrame
// Remove ASCII control characters that are used for syntax highlighting
.replace(
// eslint-disable-next-line no-control-regex
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, // https://stackoverflow.com/a/29497680
'',
)
.split('\n')
// Remove line that is supposed to indicate where the error happened
.filter(line => !line.match(/^\s*\|/))
// Find the error line
.map(line => ({
line,
isErrorLine: !!line.match(/^>/),
}))
// Remove the leading part that is just for prettier output
.map(lineObj => ({
...lineObj,
line: lineObj.line.replace(/^.*\|/, ''),
}));
const preContextLines = [];
let contextLine = undefined;
const postContextLines = [];
let reachedContextLine = false;
for (const preProcessedLine of preProcessedLines) {
if (preProcessedLine.isErrorLine) {
contextLine = preProcessedLine.line;
reachedContextLine = true;
} else if (reachedContextLine) {
postContextLines.push(preProcessedLine.line);
} else {
preContextLines.push(preProcessedLine.line);
}
}
return {
contextLine,
preContextLines,
postContextLines,
};
}
/**
* Event processor that will symbolicate errors by using the webpack/nextjs dev server that is used to show stack traces
* in the dev overlay.
*/
async function devErrorSymbolicationEventProcessor(event, hint) {
// Due to changes across Next.js versions, there are a million things that can go wrong here so we just try-catch the // entire event processor.Symbolicated stack traces are just a nice to have.
try {
if (hint.originalException && hint.originalException instanceof Error && hint.originalException.stack) {
const frames = stackTraceParser.parse(hint.originalException.stack);
const resolvedFrames = await Promise.all(
frames.map(frame => resolveStackFrame(frame, hint.originalException )),
);
if (_optionalChain([event, 'access', _7 => _7.exception, 'optionalAccess', _8 => _8.values, 'optionalAccess', _9 => _9[0], 'access', _10 => _10.stacktrace, 'optionalAccess', _11 => _11.frames])) {
event.exception.values[0].stacktrace.frames = event.exception.values[0].stacktrace.frames.map(
(frame, i, frames) => {
const resolvedFrame = resolvedFrames[frames.length - 1 - i];
if (!resolvedFrame || !resolvedFrame.originalStackFrame || !resolvedFrame.originalCodeFrame) {
return {
...frame,
platform: _optionalChain([frame, 'access', _12 => _12.filename, 'optionalAccess', _13 => _13.startsWith, 'call', _14 => _14('node:internal')]) ? 'nodejs' : undefined, // simple hack that will prevent a source mapping error from showing up
in_app: false,
};
}
const { contextLine, preContextLines, postContextLines } = parseOriginalCodeFrame(
resolvedFrame.originalCodeFrame,
);
return {
...frame,
pre_context: preContextLines,
context_line: contextLine,
post_context: postContextLines,
function: resolvedFrame.originalStackFrame.methodName,
filename: resolvedFrame.originalStackFrame.file || undefined,
lineno: resolvedFrame.originalStackFrame.lineNumber || undefined,
colno: resolvedFrame.originalStackFrame.column || undefined,
};
},
);
}
}
} catch (e) {
return event;
}
return event;
}
exports.devErrorSymbolicationEventProcessor = devErrorSymbolicationEventProcessor;
//# sourceMappingURL=devErrorSymbolicationEventProcessor.js.map