From 21c176a69ee04c4d59baedb79017f6c42ece22d6 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Fri, 25 Aug 2017 18:08:37 +0200 Subject: add rudimentary error reporting in a new tab --- src/logging.ts | 45 +++++++++++++++++++++++++++++++-- src/webex/messages.ts | 8 ++++++ src/webex/pages/error.tsx | 63 ++++++++++++++++++++++++++++++++++------------- src/webex/wxApi.ts | 8 ++++++ src/webex/wxBackend.ts | 9 +++++++ 5 files changed, 114 insertions(+), 19 deletions(-) diff --git a/src/logging.ts b/src/logging.ts index a589c8091..2c559e8d9 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -208,6 +208,44 @@ export async function recordException(msg: string, e: any): Promise { 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 { + const uid = getInsecureUuid(); + reportCache[uid] = report; + return uid; +} + + +/** + * Retrieve a report by its unique identifier. + */ +export async function getReport(reportUid: string): Promise { + 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 { 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 = new Store("logs"); @@ -283,7 +323,8 @@ export function openLoggingDb(): Promise { console.error(e); } } - resDb.createObjectStore("logs", {keyPath: "id", autoIncrement: true}); + resDb.createObjectStore("logs", { keyPath: "id", autoIncrement: true }); + resDb.createObjectStore("reports", { keyPath: "uid", autoIncrement: false }); }; }); } diff --git a/src/webex/messages.ts b/src/webex/messages.ts index d7ecd06a1..397e8876e 100644 --- a/src/webex/messages.ts +++ b/src/webex/messages.ts @@ -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; + }; } /** diff --git a/src/webex/pages/error.tsx b/src/webex/pages/error.tsx index e86b6cf4c..3f3940d72 100644 --- a/src/webex/pages/error.tsx +++ b/src/webex/pages/error.tsx @@ -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 { render(): JSX.Element { - return ( -
- An error occurred: {this.props.message} -
- ); + const report = this.props.report; + if (!report) { + return ( +
+

Error Report Not Found

+

This page is supposed to display an error reported by the GNU Taler wallet, + but the corresponding error report can't be found.

+

Maybe the error occured before the browser was restarted or the wallet was reloaded.

+
+ ); + } + switch (report.name) { + default: + return ( +
+

Unknown Error

+ The GNU Taler wallet reported an unknown error. Here are the details: +
+              {JSON.stringify(report, null, " ")}
+            
+
+ ); + } } } async function main() { - try { - const url = new URI(document.location.href); - const query: any = URI.parseQuery(url.query()); + const url = new URI(document.location.href); + const query: any = URI.parseQuery(url.query()); - const message: string = query.message || "unknown error"; + const container = document.getElementById("container"); + if (!container) { + console.error("fatal: can't mount component, countainer missing"); + return; + } - ReactDOM.render(, document.getElementById( - "container")!); + // report that we'll render, either looked up from the + // logging module or synthesized here for fixed/fatal errors + let report; - } 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 reportUid: string = query.reportUid; + if (!reportUid) { + report = { + name: "missing-error", + }; + } else { + report = await wxApi.getReport(reportUid); } + + ReactDOM.render(, container); } document.addEventListener("DOMContentLoaded", () => main()); diff --git a/src/webex/wxApi.ts b/src/webex/wxApi.ts index 1371e27e4..306406a1a 100644 --- a/src/webex/wxApi.ts +++ b/src/webex/wxApi.ts @@ -321,3 +321,11 @@ export function getSenderWireInfos(): Promise { export function returnCoins(args: { amount: AmountJson, exchange: string, senderWire: object }): Promise { return callBackend("return-coins", args); } + +export function logAndDisplayError(args: any): Promise { + return callBackend("log-and-display-error", args); +} + +export function getReport(reportUid: string): Promise { + return callBackend("get-report", { reportUid }); +} diff --git a/src/webex/wxBackend.ts b/src/webex/wxBackend.ts index 974bcb3c2..353961ff0 100644 --- a/src/webex/wxBackend.ts +++ b/src/webex/wxBackend.ts @@ -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 -- cgit v1.2.3