mirror of
https://github.com/lax1dude/crashreport-viewer.git
synced 2024-11-09 11:16:05 -08:00
461 lines
12 KiB
JavaScript
461 lines
12 KiB
JavaScript
"use strict";
|
|
|
|
const sourceMap = window.sourceMap;
|
|
|
|
sourceMap.SourceMapConsumer.initialize({ "lib/mappings.wasm": "mappings.wasm" });
|
|
|
|
var appElements = null;
|
|
|
|
const sourceMapURLs = new Map();
|
|
const sourceMapLoaded = new Map();
|
|
|
|
var isShowingOriginal = true;
|
|
var hasInput = false;
|
|
|
|
var dotsInterval = -1;
|
|
var dotsCounter = 1;
|
|
|
|
function hideDots() {
|
|
if(dotsInterval !== -1) {
|
|
clearInterval(dotsInterval);
|
|
dotsInterval = -1;
|
|
}
|
|
}
|
|
|
|
function sourceMapListLoaded(jsonFile) {
|
|
var selector = appElements.sourceMaps;
|
|
selector.disabled = false;
|
|
while(selector.firstChild) {
|
|
selector.removeChild(selector.firstChild);
|
|
}
|
|
const mapsList = jsonFile.sourceMaps;
|
|
if(mapsList.length > 0) {
|
|
var customOption = document.createElement("option");
|
|
customOption.value = "_custom";
|
|
customOption.appendChild(document.createTextNode("upload..."));
|
|
selector.appendChild(customOption);
|
|
for(var i = 0; i < mapsList.length; ++i) {
|
|
var name = mapsList[i].name;
|
|
sourceMapURLs.set(name, mapsList[i].url);
|
|
var newOption = document.createElement("option");
|
|
newOption.value = name;
|
|
newOption.appendChild(document.createTextNode(name));
|
|
selector.appendChild(newOption);
|
|
}
|
|
selector.value = mapsList[0].name;
|
|
}
|
|
hideDots();
|
|
appElements.fetchingMessage.style.display = "none";
|
|
appElements.inputTextArea.style.display = "block";
|
|
appElements.outputTextArea.style.display = "none";
|
|
}
|
|
|
|
function sourceMapListError(err) {
|
|
hideDots();
|
|
appElements.fetchingMessage.style.color = "#FF7777";
|
|
appElements.fetchingMessage.innerText = "Failed to load source map list!";
|
|
console.error(err);
|
|
}
|
|
|
|
function updateDots() {
|
|
appElements.loadingDots.innerText = ".".repeat(dotsCounter);
|
|
dotsCounter = (dotsCounter + 1) % 4;
|
|
}
|
|
|
|
function extractVersionFromFile(lines) {
|
|
for(var i = 0; i < lines.length; ++i) {
|
|
var s = lines[i].trim();
|
|
if(s.startsWith("eaglercraft.version") || s.startsWith("version")) {
|
|
var v = s.split("=");
|
|
if(v.length === 2) {
|
|
var eq = v[1].trim();
|
|
if(eq.startsWith("\"")) {
|
|
eq = eq.substring(1);
|
|
}
|
|
if(eq.endsWith("\"")) {
|
|
eq = eq.substring(0, eq.length - 1);
|
|
}
|
|
return eq;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
var updateTimeout = -1;
|
|
var lastValue = "";
|
|
var hadPaste = false;
|
|
|
|
function textAreaInputHandler() {
|
|
if(updateTimeout !== -1) {
|
|
clearTimeout(updateTimeout);
|
|
}
|
|
setTimeout(() => {
|
|
const oldValue = lastValue;
|
|
var newLength = (lastValue = appElements.inputTextArea.value).trim().length;
|
|
if(oldValue.trim().length == 0) {
|
|
if(newLength > 0) {
|
|
if(hadPaste) {
|
|
hadPaste = false;
|
|
var vers = extractVersionFromFile(lastValue.split(/\r?\n/g));
|
|
if(vers !== null) {
|
|
for(var k of sourceMapURLs.keys()) {
|
|
if(vers === k) {
|
|
appElements.sourceMaps.value = k;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
setButton(true);
|
|
}
|
|
}
|
|
}
|
|
if(newLength > 0) {
|
|
if(!hasInput) {
|
|
appElements.showOriginal.style.display = "inline";
|
|
appElements.showDecoded.style.display = "inline";
|
|
hasInput = true;
|
|
}
|
|
appElements.showDecoded.classList.remove("toggleShowDisabled");
|
|
}else {
|
|
appElements.showDecoded.classList.add("toggleShowDisabled");
|
|
}
|
|
}, 300);
|
|
}
|
|
|
|
function textAreaPasteHandler() {
|
|
textAreaInputHandler();
|
|
hadPaste = true;
|
|
}
|
|
|
|
function clearDecodedView() {
|
|
var cnt = appElements.outputContent;
|
|
while(cnt.firstChild) {
|
|
cnt.removeChild(cnt.firstChild);
|
|
}
|
|
}
|
|
|
|
function showInfo(str, err) {
|
|
clearDecodedView();
|
|
var span = document.createElement("span");
|
|
span.appendChild(document.createTextNode(str));
|
|
span.style.fontSize = "20px";
|
|
if(err) {
|
|
span.style.color = "#FF9999";
|
|
}
|
|
appElements.outputContent.appendChild(span);
|
|
}
|
|
|
|
function highlightLine(line) {
|
|
var e = document.createElement("span");
|
|
e.style.color = "#FFFF77";
|
|
e.appendChild(document.createTextNode(line));
|
|
return e;
|
|
}
|
|
|
|
function formatLine(srcMapLine) {
|
|
if(srcMapLine.line !== null) {
|
|
if(srcMapLine.name === null) {
|
|
return (srcMapLine.source === null ? "<unknown>" : srcMapLine.source) + ":" +
|
|
srcMapLine.line + (srcMapLine.column > 0 ? ":" + srcMapLine.column : "");
|
|
}else {
|
|
return "" + srcMapLine.name + " (" + (srcMapLine.source === null ? "<unknown>" :
|
|
srcMapLine.source) + ":" + srcMapLine.line + ":" + (srcMapLine.column > 0 ? ":" +
|
|
srcMapLine.column : "") + ")";
|
|
}
|
|
}else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function getIndent(line) {
|
|
var ret = "";
|
|
for(var i = 0; i < line.length; ++i) {
|
|
var c = line.charAt(i);
|
|
if(c === " " || c === "\t") {
|
|
ret += c;
|
|
}else {
|
|
break;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
function makeBold(str) {
|
|
return "<span style=\"font-weight:bold;font-size:19px;\">" + str.replace("<", "<").replace(">", ">") + "</span>";
|
|
}
|
|
|
|
function printVersionWarning(vers) {
|
|
const v = appElements.sourceMaps.value;
|
|
if(sourceMapURLs.get(v) === "LOCAL") {
|
|
return;
|
|
}
|
|
var str = null;
|
|
if(!vers) {
|
|
str = "WARNING: no game version (eaglercraft.version) is in this crash report!"
|
|
}else {
|
|
if(v !== vers) {
|
|
str = "WARNING: this crash report seems to be from version '" + makeBold(vers) + "', but you have source map version '" + makeBold(v) + "' selected!";
|
|
}
|
|
}
|
|
if(str !== null) {
|
|
var e = document.createElement("span");
|
|
e.style.color = "#FF7777";
|
|
e.style.fontSize = "16px";
|
|
e.innerHTML = str + "\n\nIf you don't select the correct source map version then the crash report will be incorrect\n\n";
|
|
appElements.outputContent.appendChild(e);
|
|
}
|
|
}
|
|
|
|
function updateSource(srcMap) {
|
|
clearDecodedView();
|
|
var sourceValue = appElements.inputTextArea.value;
|
|
var lines = sourceValue.split(/\r?\n/g);
|
|
var vers = extractVersionFromFile(lines);
|
|
var hasShownWarning = false;
|
|
for(var i = 0; i < lines.length; ++i) {
|
|
var l = lines[i];
|
|
|
|
if(l.indexOf("eagswebrtc") === -1) {
|
|
var split = l.split(":");
|
|
|
|
if(split.length > 1) {
|
|
var firstToken = split[0].toLowerCase();
|
|
if(firstToken.endsWith("error")) {
|
|
if(!hasShownWarning) {
|
|
hasShownWarning = true;
|
|
printVersionWarning(vers);
|
|
}
|
|
appElements.outputContent.appendChild(highlightLine(l + "\n"));
|
|
continue;
|
|
}else if(split.length > 2) {
|
|
var lineTrim = split[split.length - 2].trim();
|
|
var lineNo = parseInt(lineTrim);
|
|
var colTrim = split[split.length - 1].trim();
|
|
var colNo = parseInt(colTrim);
|
|
if(isNaN(colNo)) {
|
|
if(colTrim.length > 1) {
|
|
colNo = parseInt(colTrim.substring(0, colTrim.length - 1));
|
|
}
|
|
}
|
|
if(!isNaN(lineNo) && !isNaN(colNo)) {
|
|
var original = formatLine(srcMap.originalPositionFor({ line: lineNo, column: colNo }));
|
|
if(original !== null) {
|
|
if(firstToken.endsWith("line")) {
|
|
appElements.outputContent.appendChild(document.createTextNode(lines[i] + " "));
|
|
appElements.outputContent.appendChild(highlightLine(original + "\n"));
|
|
}else {
|
|
if(!hasShownWarning) {
|
|
hasShownWarning = true;
|
|
printVersionWarning(vers);
|
|
}
|
|
var idt = getIndent(split[0]);
|
|
var realStart = split[0].substring(idt.length);
|
|
if(realStart.startsWith("at")) {
|
|
appElements.outputContent.appendChild(highlightLine(idt + "at " + original + "\n"));
|
|
}else {
|
|
appElements.outputContent.appendChild(highlightLine(idt + original + "\n"));
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
appElements.outputContent.appendChild(document.createTextNode(lines[i] + "\n"));
|
|
}
|
|
}
|
|
|
|
function tryUpdateSource(srcMap) {
|
|
sourceMap.SourceMapConsumer.with(srcMap, null, (srcMapObject) => {
|
|
try {
|
|
updateSource(srcMapObject);
|
|
}catch(e) {
|
|
isUpdating = false;
|
|
showInfo("Parse error, check devtools", true);
|
|
console.error(e);
|
|
}
|
|
}).catch((e) => {
|
|
isUpdating = false;
|
|
showInfo("Source map load error, check devtools", true);
|
|
console.error(e);
|
|
});
|
|
}
|
|
|
|
var isUpdating = false;
|
|
|
|
function updateDecodedPane() {
|
|
if(!isUpdating) {
|
|
const v = appElements.sourceMaps.value;
|
|
if(v !== "_custom") {
|
|
var mapJSON = sourceMapLoaded.get(v);
|
|
|
|
if(mapJSON) {
|
|
try {
|
|
showInfo("Processing source...");
|
|
tryUpdateSource(mapJSON);
|
|
return;
|
|
}catch(e) {
|
|
isUpdating = false;
|
|
console.error(e);
|
|
showInfo("Internal error, check devtools", true);
|
|
}
|
|
}
|
|
|
|
var mapURL = sourceMapURLs.get(v);
|
|
|
|
if(!mapURL) {
|
|
showInfo("Unknown source map: " + v, true);
|
|
isUpdating = false;
|
|
return;
|
|
}
|
|
|
|
showInfo("Loading source map " + v + "...");
|
|
|
|
fetch(mapURL)
|
|
.then((r) => r.json()).then((j) => {
|
|
sourceMapLoaded.set(v, j);
|
|
try {
|
|
showInfo("Processing source...");
|
|
tryUpdateSource(j);
|
|
}catch(e) {
|
|
isUpdating = false;
|
|
showInfo("Internal error, check devtools", true);
|
|
}
|
|
}).catch((e) => {
|
|
isUpdating = false;
|
|
showInfo("Could not load source map: " + v, true);
|
|
});
|
|
}else {
|
|
clearDecodedView();
|
|
showFileChooser();
|
|
}
|
|
}
|
|
}
|
|
|
|
function setButton(decodedOrOriginal) {
|
|
if(decodedOrOriginal) {
|
|
if(isShowingOriginal) {
|
|
isShowingOriginal = false;
|
|
appElements.showDecoded.classList.add("toggleSelected");
|
|
appElements.showOriginal.classList.remove("toggleSelected");
|
|
appElements.inputTextArea.style.display = "none";
|
|
appElements.outputTextArea.style.display = "block";
|
|
updateDecodedPane();
|
|
}
|
|
}else {
|
|
if(!isShowingOriginal) {
|
|
isShowingOriginal = true;
|
|
appElements.showDecoded.classList.remove("toggleSelected");
|
|
appElements.showOriginal.classList.add("toggleSelected");
|
|
appElements.inputTextArea.style.display = "block";
|
|
appElements.outputTextArea.style.display = "none";
|
|
}
|
|
}
|
|
}
|
|
|
|
function comboBoxChangeHandler() {
|
|
if(!isShowingOriginal) {
|
|
updateDecodedPane();
|
|
}
|
|
}
|
|
|
|
function showFileChooser() {
|
|
const fileChooserElement = document.createElement("input");
|
|
|
|
fileChooserElement.type = "file";
|
|
fileChooserElement.accept = ".json,.map";
|
|
|
|
fileChooserElement.addEventListener("change", () => {
|
|
const files = fileChooserElement.files;
|
|
if(files.length > 0) {
|
|
var phile = files[0];
|
|
var name = phile.name.trim();
|
|
if(name.endsWith(".json")) {
|
|
name = name.substring(0, name.length - 5).trim();
|
|
}
|
|
if(name.length > 0) {
|
|
if(name === "_custom") {
|
|
name = "__custom";
|
|
}
|
|
var name2 = name;
|
|
var i = 0;
|
|
while(sourceMapURLs.has(name2)) {
|
|
name2 = name + " (" + (++i) + ")";
|
|
}
|
|
const namec = name2;
|
|
const reader = new FileReader();
|
|
reader.addEventListener("load", () => {
|
|
try {
|
|
var jsonObj = JSON.parse(reader.result);
|
|
if(jsonObj) {
|
|
sourceMapURLs.set(namec, "LOCAL");
|
|
sourceMapLoaded.set(namec, jsonObj);
|
|
var newOption = document.createElement("option");
|
|
newOption.value = namec;
|
|
newOption.appendChild(document.createTextNode(namec));
|
|
const selector = appElements.sourceMaps;
|
|
if(selector.childNodes.length > 1) {
|
|
selector.insertBefore(newOption, selector.childNodes[1]);
|
|
}else {
|
|
selector.appendChild(newOption);
|
|
}
|
|
selector.value = namec;
|
|
if(!isShowingOriginal) {
|
|
updateDecodedPane();
|
|
}
|
|
}
|
|
}catch(e) {
|
|
console.log(e);
|
|
alert("Not a valid JSON source map!");
|
|
}
|
|
});
|
|
reader.readAsText(phile);
|
|
}
|
|
}
|
|
});
|
|
|
|
fileChooserElement.click();
|
|
}
|
|
|
|
window.addEventListener("load", () => {
|
|
|
|
appElements = {
|
|
showOriginal: document.getElementById("showOriginal"),
|
|
showDecoded: document.getElementById("showDecoded"),
|
|
sourceMaps: document.getElementById("sourceMaps"),
|
|
inputTextArea: document.getElementById("inputTextArea"),
|
|
outputTextArea: document.getElementById("outputTextArea"),
|
|
outputContent: document.getElementById("outputContent"),
|
|
fetchingMessage: document.getElementById("fetchingMessage"),
|
|
loadingDots: document.getElementById("loadingDots")
|
|
};
|
|
|
|
dotsInterval = setInterval(updateDots, 300);
|
|
|
|
appElements.showOriginal.addEventListener("click", () => setButton(false));
|
|
|
|
appElements.showDecoded.addEventListener("click", () => {
|
|
if(appElements.inputTextArea.value.trim().length > 0) {
|
|
setButton(true);
|
|
}
|
|
});
|
|
|
|
appElements.inputTextArea.addEventListener("propertychange", textAreaInputHandler);
|
|
appElements.inputTextArea.addEventListener("change", textAreaInputHandler);
|
|
appElements.inputTextArea.addEventListener("click", textAreaInputHandler);
|
|
appElements.inputTextArea.addEventListener("keyup", textAreaInputHandler);
|
|
appElements.inputTextArea.addEventListener("input", textAreaInputHandler);
|
|
appElements.inputTextArea.addEventListener("paste", textAreaPasteHandler);
|
|
|
|
appElements.sourceMaps.addEventListener("change", comboBoxChangeHandler);
|
|
|
|
fetch("sourceMaps.json")
|
|
.then((r) => r.json())
|
|
.then(sourceMapListLoaded)
|
|
.catch(sourceMapListError);
|
|
|
|
});
|