Skip to content

Commit f4d4769

Browse files
committed
Merge branch 'offlineQt'
2 parents 1577e22 + ced36b8 commit f4d4769

File tree

16 files changed

+202
-5
lines changed

16 files changed

+202
-5
lines changed

backend/backend.go

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"path/filepath"
2525
"slices"
2626
"strings"
27+
"sync/atomic"
2728
"time"
2829

2930
"github.com/BitBoxSwiss/bitbox-wallet-app/backend/accounts"
@@ -235,6 +236,9 @@ type Backend struct {
235236

236237
// testing tells us whether the app is in testing mode
237238
testing bool
239+
240+
// isOnline indicates whether the backend is online, i.e. able to connect to the internet.
241+
isOnline atomic.Bool
238242
}
239243

240244
// NewBackend creates a new backend with the given arguments.
@@ -276,6 +280,8 @@ func NewBackend(arguments *arguments.Arguments, environment Environment) (*Backe
276280

277281
testing: backendConfig.AppConfig().Backend.StartInTestnet || arguments.Testing(),
278282
}
283+
// TODO: remove when connectivity check is present on all platforms
284+
backend.isOnline.Store(true)
279285

280286
notifier, err := NewNotifier(filepath.Join(arguments.MainDirectoryPath(), "notifier.db"))
281287
if err != nil {
@@ -549,7 +555,7 @@ func (backend *Backend) Coin(code coinpkg.Code) (coinpkg.Coin, error) {
549555
// ManualReconnect triggers reconnecting to Electrum servers if their connection is down.
550556
// Only coin connections that were previously established are reconnected.
551557
// Calling this is a no-op for coins that are already connected.
552-
func (backend *Backend) ManualReconnect() {
558+
func (backend *Backend) ManualReconnect(reconnectETH bool) {
553559
var electrumCoinCodes []coinpkg.Code
554560
if backend.Testing() {
555561
electrumCoinCodes = []coinpkg.Code{
@@ -581,6 +587,16 @@ func (backend *Backend) ManualReconnect() {
581587
}
582588
blockchain.ManualReconnect()
583589
}
590+
if reconnectETH {
591+
backend.log.Info("Reconnecting ETH accounts")
592+
for _, account := range backend.accounts {
593+
ethAccount, ok := account.(*eth.Account)
594+
if !ok {
595+
continue
596+
}
597+
ethAccount.EnqueueUpdate()
598+
}
599+
}
584600
}
585601

586602
// Testing returns whether this backend is for testing only.
@@ -943,6 +959,26 @@ func (backend *Backend) HandleURI(uri string) {
943959
}
944960
}
945961

962+
// SetOnline sets the backend's online status and notifies the frontend.
963+
func (backend *Backend) SetOnline(online bool) {
964+
backend.log.Infof("Setting online status to %v", online)
965+
// If coming back online, trigger a reconnection.
966+
if online && !backend.isOnline.Load() {
967+
backend.ManualReconnect(true)
968+
}
969+
backend.isOnline.Store(online)
970+
backend.Notify(observable.Event{
971+
Subject: "online",
972+
Action: action.Reload,
973+
Object: online,
974+
})
975+
}
976+
977+
// IsOnline returns whether the backend is online, i.e. able to connect to the internet.
978+
func (backend *Backend) IsOnline() bool {
979+
return backend.isOnline.Load()
980+
}
981+
946982
// GetAccountFromCode takes an account code as input and returns the corresponding accounts.Interface object,
947983
// if found. It also initialize the account before returning it.
948984
func (backend *Backend) GetAccountFromCode(acctCode accountsTypes.Code) (accounts.Interface, error) {

backend/bridgecommon/bridgecommon.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,16 @@ func HandleURI(uri string) {
123123
globalBackend.HandleURI(uri)
124124
}
125125

126+
// SetOnline should be called when the network connection changed.
127+
func SetOnline(isOnline bool) {
128+
mu.RLock()
129+
defer mu.RUnlock()
130+
if globalBackend == nil {
131+
return
132+
}
133+
globalBackend.SetOnline(isOnline)
134+
}
135+
126136
// TriggerAuth triggers an authentication request notification.
127137
func TriggerAuth() {
128138
mu.Lock()
@@ -176,7 +186,9 @@ func ManualReconnect() {
176186
if globalBackend == nil {
177187
return
178188
}
179-
globalBackend.ManualReconnect()
189+
// We pass false as by default we do not want
190+
// to reconnect ETH accounts.
191+
globalBackend.ManualReconnect(false)
180192

181193
}
182194

backend/coins/eth/account.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -714,7 +714,7 @@ func (account *Account) SendTx(txNote string) error {
714714
// Not critical.
715715
account.log.WithError(err).Error("Failed to save transaction note when sending a tx")
716716
}
717-
account.enqueueUpdateCh <- struct{}{}
717+
account.EnqueueUpdate()
718718
return nil
719719
}
720720

@@ -1024,3 +1024,11 @@ func (account *Account) MatchesAddress(address string) (bool, error) {
10241024
}
10251025
return false, nil
10261026
}
1027+
1028+
// EnqueueUpdate enqueues an update for the account.
1029+
func (account *Account) EnqueueUpdate() {
1030+
select {
1031+
case account.enqueueUpdateCh <- struct{}{}:
1032+
default:
1033+
}
1034+
}

backend/handlers/handlers.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ type Backend interface {
116116
SetWatchonly(rootFingerprint []byte, watchonly bool) error
117117
LookupEthAccountCode(address string) (accountsTypes.Code, string, error)
118118
Bluetooth() *bluetooth.Bluetooth
119+
IsOnline() bool
119120
}
120121

121122
// Handlers provides a web api to the backend.
@@ -262,6 +263,8 @@ func NewHandlers(
262263
getAPIRouterNoError(apiRouter)("/bluetooth/state", handlers.getBluetoothState).Methods("GET")
263264
getAPIRouterNoError(apiRouter)("/bluetooth/connect", handlers.postBluetoothConnect).Methods("POST")
264265

266+
getAPIRouterNoError(apiRouter)("/online", handlers.getOnline).Methods("GET")
267+
265268
devicesRouter := getAPIRouterNoError(apiRouter.PathPrefix("/devices").Subrouter())
266269
devicesRouter("/registered", handlers.getDevicesRegistered).Methods("GET")
267270

@@ -1622,3 +1625,7 @@ func (handlers *Handlers) postBluetoothConnect(r *http.Request) interface{} {
16221625
handlers.backend.Environment().BluetoothConnect(identifier)
16231626
return nil
16241627
}
1628+
1629+
func (handlers *Handlers) getOnline(r *http.Request) interface{} {
1630+
return handlers.backend.IsOnline()
1631+
}

frontends/qt/BitBox.pro

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
QT += core gui
88
QT += webenginewidgets
9+
QT += network
910
win32 {
1011
# For setting 'AlwaysActivate', see.
1112
# See https://forum.qt.io/topic/133694/using-alwaysactivatewindow-to-gain-foreground-in-win10-using-qt6-2/2
@@ -60,9 +61,10 @@ unix:!macx {
6061
SOURCES += \
6162
main.cpp \
6263
filedialog.cpp \
63-
urlhandler.cpp
64+
urlhandler.cpp \
65+
network.cpp
6466

65-
HEADERS += libserver.h webclass.h filedialog.h urlhandler.h
67+
HEADERS += libserver.h webclass.h filedialog.h urlhandler.h network.h
6668

6769
unix:macx {
6870
CONFIG += sdk_no_version_check

frontends/qt/libserver.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <string.h>
44
#include <stdint.h>
55
#include <stdlib.h>
6+
#include <stdbool.h>
67

78
// Workaround to be able to use `const char*` as a param type in the exported Go functions.
89
typedef const char cchar_t;
@@ -45,6 +46,7 @@ extern "C" {
4546
#endif
4647

4748
extern void backendCall(int queryID, cchar_t* s);
49+
extern void setOnline(bool online);
4850
extern void handleURI(cchar_t* uri);
4951
extern void serve(cppHeapFree cppHeapFreeFn, pushNotificationsCallback pushNotificationsFn, responseCallback responseFn, notifyUserCallback notifyUserFn, cchar_t* preferredLocale, getSaveFilenameCallback getSaveFilenameFn);
5052
extern void systemOpen(cchar_t* url);

frontends/qt/main.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
#include "libserver.h"
5858
#include "webclass.h"
5959
#include "urlhandler.h"
60+
#include "network.h"
6061

6162
#define APPNAME "BitBoxApp"
6263

@@ -371,6 +372,8 @@ int main(int argc, char *argv[])
371372

372373
webClass = new WebClass();
373374

375+
setupReachabilityNotifier();
376+
374377
serve(
375378
// cppHeapFree
376379
[](void* ptr) { ::free(ptr); },
@@ -417,6 +420,9 @@ int main(int argc, char *argv[])
417420
return result;
418421
});
419422

423+
// Trigger the online status change event once at startup.
424+
setOnline(isReachable());
425+
420426
RequestInterceptor interceptor;
421427
view->page()->profile()->setUrlRequestInterceptor(&interceptor);
422428

frontends/qt/network.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#include <QNetworkInformation>
2+
#include "libserver.h"
3+
#include "network.h"
4+
5+
void setupReachabilityNotifier() {
6+
auto loaded = QNetworkInformation::loadBackendByFeatures(QNetworkInformation::Feature::Reachability);
7+
if (!loaded) {
8+
goLog("can't load QNetworkInformation backend");
9+
// If we can't load the backend, we can't determine whether or not to show the banner.
10+
setOnline(true);
11+
return;
12+
}
13+
QNetworkInformation* info = QNetworkInformation::instance();
14+
if (info) {
15+
QObject::connect(info, &QNetworkInformation::reachabilityChanged, [](QNetworkInformation::Reachability reachability){
16+
// We include Reachability::Unknown here as we prefer not include false positives. If QT can't determine whether
17+
// we are online, we do not show the banner.
18+
bool isReachable = (reachability == QNetworkInformation::Reachability::Online || reachability == QNetworkInformation::Reachability::Unknown);
19+
setOnline(isReachable);
20+
});
21+
}
22+
}
23+
24+
bool isReachable() {
25+
auto loaded = QNetworkInformation::loadBackendByFeatures(QNetworkInformation::Feature::Reachability);
26+
if (!loaded) {
27+
goLog("can't load QNetworkInformation backend");
28+
return true;
29+
}
30+
QNetworkInformation* info = QNetworkInformation::instance();
31+
if (info) {
32+
return info->reachability() == QNetworkInformation::Reachability::Online || info->reachability() == QNetworkInformation::Reachability::Unknown;
33+
}
34+
// Same as above, if we can't obtain information, we don't show any banner at all.
35+
return true;
36+
}

frontends/qt/network.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#ifndef NETWORK_H
2+
#define NETWORK_H
3+
#include <stdbool.h>
4+
5+
// setupReachabilityNotifier connects to the QNetworkInformation::reachabilityChanged signal
6+
// in otder to react to changes in reachability.
7+
void setupReachabilityNotifier();
8+
// isReachable uses QNetworkInformation to retrieve info about the online status.
9+
// Note: it treats cases in which QT can't determine if we are online as if we were.
10+
bool isReachable();
11+
12+
#endif // NETWORK_H

frontends/qt/server/server.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,11 @@ func matchDarkTheme(themeName string) bool {
103103
return strings.Contains(strings.ToLower(themeName), "dark")
104104
}
105105

106+
//export setOnline
107+
func setOnline(isReachable bool) {
108+
bridgecommon.SetOnline(isReachable)
109+
}
110+
106111
//export serve
107112
func serve(
108113
cppHeapFreeFn C.cppHeapFree,

0 commit comments

Comments
 (0)