add rudimentary error reporting in a new tab

This commit is contained in:
Florian Dold 2017-08-25 18:08:37 +02:00
parent bf70e752b6
commit 21c176a69e
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
5 changed files with 116 additions and 21 deletions

View File

@ -208,6 +208,44 @@ export async function recordException(msg: string, e: any): Promise<void> {
return record("error", e.toString(), stack, frame.file, frame.line, frame.column);
}
/**
* Cache for reports. Also used when something is so broken that we can't even
* access the database.
*/
const reportCache: { [reportId: string]: any } = {};
/**
* Get a UUID that does not use cryptographically secure randomness.
* Formatted as RFC4122 version 4 UUID.
*/
function getInsecureUuid() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
/**
* Store a report and return a unique identifier to retrieve it later.
*/
export async function storeReport(report: any): Promise<string> {
const uid = getInsecureUuid();
reportCache[uid] = report;
return uid;
}
/**
* Retrieve a report by its unique identifier.
*/
export async function getReport(reportUid: string): Promise<any> {
return reportCache[reportUid];
}
/**
* Record a log entry in the database.
*/
@ -218,6 +256,8 @@ export async function record(level: Level,
line?: number,
col?: number): Promise<void> {
if (typeof indexedDB === "undefined") {
console.log("can't access DB for logging in this context");
console.log("log was", { level, msg, detail, source, line, col });
return;
}
@ -257,7 +297,7 @@ export async function record(level: Level,
}
}
const loggingDbVersion = 1;
const loggingDbVersion = 2;
const logsStore: Store<LogEntry> = new Store<LogEntry>("logs");
@ -283,7 +323,8 @@ export function openLoggingDb(): Promise<IDBDatabase> {
console.error(e);
}
}
resDb.createObjectStore("logs", {keyPath: "id", autoIncrement: true});
resDb.createObjectStore("logs", { keyPath: "id", autoIncrement: true });
resDb.createObjectStore("reports", { keyPath: "uid", autoIncrement: false });
};
});
}

View File

@ -176,6 +176,14 @@ export interface MessageMap {
request: { };
response: void;
};
"log-and-display-error": {
request: any;
response: void;
};
"get-report": {
request: { reportUid: string };
response: void;
};
}
/**

View File

@ -22,40 +22,69 @@
* @author Florian Dold
*/
import * as React from "react";
import * as ReactDOM from "react-dom";
import URI = require("urijs");
import * as wxApi from "../wxApi";
interface ErrorProps {
message: string;
report: any;
}
class ErrorView extends React.Component<ErrorProps, { }> {
render(): JSX.Element {
const report = this.props.report;
if (!report) {
return (
<div>
An error occurred: {this.props.message}
<h1>Error Report Not Found</h1>
<p>This page is supposed to display an error reported by the GNU Taler wallet,
but the corresponding error report can't be found.</p>
<p>Maybe the error occured before the browser was restarted or the wallet was reloaded.</p>
</div>
);
}
switch (report.name) {
default:
return (
<div>
<h1>Unknown Error</h1>
The GNU Taler wallet reported an unknown error. Here are the details:
<pre>
{JSON.stringify(report, null, " ")}
</pre>
</div>
);
}
}
}
async function main() {
try {
const url = new URI(document.location.href);
const query: any = URI.parseQuery(url.query());
const message: string = query.message || "unknown error";
ReactDOM.render(<ErrorView message={message} />, document.getElementById(
"container")!);
} catch (e) {
// TODO: provide more context information, maybe factor it out into a
// TODO:generic error reporting function or component.
document.body.innerText = `Fatal error: "${e.message}".`;
console.error(`got error "${e.message}"`, e);
const container = document.getElementById("container");
if (!container) {
console.error("fatal: can't mount component, countainer missing");
return;
}
// report that we'll render, either looked up from the
// logging module or synthesized here for fixed/fatal errors
let report;
const reportUid: string = query.reportUid;
if (!reportUid) {
report = {
name: "missing-error",
};
} else {
report = await wxApi.getReport(reportUid);
}
ReactDOM.render(<ErrorView report={report} />, container);
}
document.addEventListener("DOMContentLoaded", () => main());

View File

@ -321,3 +321,11 @@ export function getSenderWireInfos(): Promise<SenderWireInfos> {
export function returnCoins(args: { amount: AmountJson, exchange: string, senderWire: object }): Promise<void> {
return callBackend("return-coins", args);
}
export function logAndDisplayError(args: any): Promise<void> {
return callBackend("log-and-display-error", args);
}
export function getReport(reportUid: string): Promise<void> {
return callBackend("get-report", { reportUid });
}

View File

@ -303,6 +303,15 @@ function handleMessage(sender: MessageSender,
}
return resp;
}
case "log-and-display-error":
logging.storeReport(detail).then((reportUid) => {
chrome.tabs.create({
url: chrome.extension.getURL(`/src/webex/pages/error.html?reportUid=${reportUid}`),
});
});
return;
case "get-report":
return logging.getReport(detail.reportUid);
default:
// Exhaustiveness check.
// See https://www.typescriptlang.org/docs/handbook/advanced-types.html