Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/dashboard/resolver.js
2492 views
1
/**
2
* Copyright (c) 2023 Gitpod GmbH. All rights reserved.
3
* Licensed under the GNU Affero General Public License (AGPL).
4
* See License.AGPL.txt in the project root for license information.
5
*/
6
7
/*
8
* Resolver allows to reconstruct stack traces from obfuscated stack traces for the dashboard.
9
* Usage:
10
* node resolver.js < obfuscated-stack-trace.txt
11
*
12
* OR
13
*
14
* node resolver.js <<EOF
15
* obfuscated stack trace
16
* EOF
17
*/
18
19
//@ts-check
20
const path = require("path");
21
const fetch = require("node-fetch").default;
22
const { SourceMapConsumer } = require("source-map");
23
24
const sourceMapCache = {};
25
26
function extractJsUrlFromLine(line) {
27
const match = line.match(/https?:\/\/[^\s]+\.js/);
28
return match ? match[0] : null;
29
}
30
31
async function fetchSourceMap(jsUrl) {
32
// Use cached source map if available
33
if (sourceMapCache[jsUrl]) {
34
return sourceMapCache[jsUrl];
35
}
36
37
const jsResponse = await fetch(jsUrl);
38
const jsContent = await jsResponse.text();
39
40
// Extract source map URL from the JS file
41
const mapUrlMatch = jsContent.match(/\/\/#\s*sourceMappingURL=(.+)/);
42
if (!mapUrlMatch) {
43
throw new Error("Source map URL not found");
44
}
45
46
const mapUrl = new URL(mapUrlMatch[1], jsUrl).href; // Resolve relative URL
47
const mapResponse = await fetch(mapUrl);
48
const mapData = await mapResponse.json();
49
50
// Cache the fetched source map
51
sourceMapCache[jsUrl] = mapData;
52
53
return mapData;
54
}
55
56
const BASE_PATH = "/workspace/gitpod/components";
57
58
async function resolveLine(line) {
59
const jsUrl = extractJsUrlFromLine(line);
60
if (!jsUrl) return line;
61
62
const rawSourceMap = await fetchSourceMap(jsUrl);
63
const matches = line.match(/at (?:([\S]+) )?\(?(https?:\/\/[^\s]+\.js):(\d+):(\d+)\)?/);
64
65
if (!matches) {
66
return line;
67
}
68
69
const functionName = matches[1] || "";
70
const lineNum = Number(matches[3]);
71
const colNum = Number(matches[4]);
72
73
const consumer = new SourceMapConsumer(rawSourceMap);
74
const originalPosition = consumer.originalPositionFor({ line: lineNum, column: colNum });
75
76
if (originalPosition && originalPosition.source) {
77
const fullPath = path.join(BASE_PATH, originalPosition.source);
78
const originalFunctionName = originalPosition.name || functionName;
79
return ` at ${originalFunctionName} (${fullPath}:${originalPosition.line}:${originalPosition.column})`;
80
}
81
82
return line;
83
}
84
85
let obfuscatedTrace = "";
86
87
process.stdin.on("data", function (data) {
88
obfuscatedTrace += data;
89
});
90
91
process.stdin.on("end", async function () {
92
const lines = obfuscatedTrace.split("\n");
93
const resolvedLines = await Promise.all(lines.map(resolveLine));
94
const resolvedTrace = resolvedLines.join("\n");
95
console.log("\nResolved Stack Trace:\n");
96
console.log(resolvedTrace);
97
});
98
99
if (process.stdin.isTTY) {
100
console.error("Please provide the obfuscated stack trace either as a multi-line input or from a file.");
101
process.exit(1);
102
}
103
104