diff --git a/electrum/base_crash_reporter.py b/electrum/base_crash_reporter.py
index b3441afc1..b2c95be80 100644
--- a/electrum/base_crash_reporter.py
+++ b/electrum/base_crash_reporter.py
@@ -25,6 +25,7 @@ import locale
import traceback
import sys
import queue
+from typing import NamedTuple, Optional
from .version import ELECTRUM_VERSION
from . import constants
@@ -33,6 +34,12 @@ from .util import make_aiohttp_session
from .logging import describe_os_version, Logger, get_git_version
+class CrashReportResponse(NamedTuple):
+ status: Optional[str]
+ text: str
+ url: Optional[str]
+
+
class BaseCrashReporter(Logger):
report_server = "https://crashhub.electrum.org"
config_key = "show_crash_reporter"
@@ -63,16 +70,33 @@ class BaseCrashReporter(Logger):
Logger.__init__(self)
self.exc_args = (exctype, value, tb)
- def send_report(self, asyncio_loop, proxy, endpoint="/crash", *, timeout=None):
+ def send_report(self, asyncio_loop, proxy, *, timeout=None) -> CrashReportResponse:
+ # FIXME the caller needs to catch generic "Exception", as this method does not have a well-defined API...
if constants.net.GENESIS[-4:] not in ["4943", "e26f"] and ".electrum.org" in BaseCrashReporter.report_server:
# Gah! Some kind of altcoin wants to send us crash reports.
raise Exception(_("Missing report URL."))
report = self.get_traceback_info()
report.update(self.get_additional_info())
report = json.dumps(report)
- coro = self.do_post(proxy, BaseCrashReporter.report_server + endpoint, data=report)
+ coro = self.do_post(proxy, BaseCrashReporter.report_server + "/crash.json", data=report)
response = asyncio.run_coroutine_threadsafe(coro, asyncio_loop).result(timeout)
- return response
+ self.logger.info(f"Crash report sent. Got response [DO NOT TRUST THIS MESSAGE]: {response!r}")
+ response = json.loads(response)
+ assert isinstance(response, dict), type(response)
+ # sanitize URL
+ if location := response.get("location"):
+ assert isinstance(location, str)
+ base_issues_url = constants.GIT_REPO_ISSUES_URL
+ if not base_issues_url.endswith("/"):
+ base_issues_url = base_issues_url + "/"
+ if not location.startswith(base_issues_url):
+ location = None
+ ret = CrashReportResponse(
+ status=response.get("status"),
+ url=location,
+ text=_("Thanks for reporting this issue!"),
+ )
+ return ret
async def do_post(self, proxy, url, data):
async with make_aiohttp_session(proxy) as session:
diff --git a/electrum/gui/kivy/uix/dialogs/crash_reporter.py b/electrum/gui/kivy/uix/dialogs/crash_reporter.py
index 49b23b78d..f5aa0a9da 100644
--- a/electrum/gui/kivy/uix/dialogs/crash_reporter.py
+++ b/electrum/gui/kivy/uix/dialogs/crash_reporter.py
@@ -121,16 +121,16 @@ class CrashReporter(BaseCrashReporter, Factory.Popup):
loop = self.main_window.network.asyncio_loop
proxy = self.main_window.network.proxy
# FIXME network request in GUI thread...
- response = json.loads(BaseCrashReporter.send_report(self, loop, proxy,
- "/crash.json", timeout=10))
- except (ValueError, ClientError) as e:
+ response = BaseCrashReporter.send_report(self, loop, proxy, timeout=10)
+ except Exception as e:
self.logger.warning(f"Error sending crash report. exc={e!r}")
self.show_popup(_('Unable to send report'), _("Please check your network connection."))
else:
- self.show_popup(_('Report sent'), response["text"])
- location = response["location"]
- if location:
- self.logger.info(f"Crash report sent. location={location!r}")
+ text = response.text
+ if response.url:
+ text += f" You can track further progress on GitHub."
+ self.show_popup(_('Report sent'), text)
+ if location := response.url:
self.open_url(location)
self.dismiss()
diff --git a/electrum/gui/qml/qeapp.py b/electrum/gui/qml/qeapp.py
index d151f1a27..8dc9077e3 100644
--- a/electrum/gui/qml/qeapp.py
+++ b/electrum/gui/qml/qeapp.py
@@ -247,13 +247,17 @@ class QEAppController(BaseCrashReporter, QObject):
def report_task():
try:
response = BaseCrashReporter.send_report(self, network.asyncio_loop, proxy)
- self.sendingBugreportSuccess.emit(response)
except Exception as e:
self.logger.error('There was a problem with the automatic reporting', exc_info=e)
self.sendingBugreportFailure.emit(_('There was a problem with the automatic reporting:') + '
' +
repr(e)[:120] + '
' +
_("Please report this issue manually") +
f' on GitHub.')
+ else:
+ text = response.text
+ if response.url:
+ text += f" You can track further progress on GitHub."
+ self.sendingBugreportSuccess.emit(text)
self.sendingBugreport.emit()
threading.Thread(target=report_task).start()
diff --git a/electrum/gui/qt/exception_window.py b/electrum/gui/qt/exception_window.py
index 1a920aa15..d549d75d1 100644
--- a/electrum/gui/qt/exception_window.py
+++ b/electrum/gui/qt/exception_window.py
@@ -31,7 +31,7 @@ from PyQt5.QtWidgets import (QWidget, QLabel, QPushButton, QTextEdit,
QMessageBox, QHBoxLayout, QVBoxLayout)
from electrum.i18n import _
-from electrum.base_crash_reporter import BaseCrashReporter, EarlyExceptionsQueue
+from electrum.base_crash_reporter import BaseCrashReporter, EarlyExceptionsQueue, CrashReportResponse
from electrum.logging import Logger
from electrum import constants
from electrum.network import Network
@@ -103,12 +103,13 @@ class Exception_Window(BaseCrashReporter, QWidget, MessageBoxMixin, Logger):
self.show()
def send_report(self):
- def on_success(response):
- # note: 'response' coming from (remote) crash reporter server.
- # It contains a URL to the GitHub issue, so we allow rich text.
+ def on_success(response: CrashReportResponse):
+ text = response.text
+ if response.url:
+ text += f" You can track further progress on GitHub."
self.show_message(parent=self,
title=_("Crash report"),
- msg=response,
+ msg=text,
rich_text=True)
self.close()
def on_failure(exc_info):