From d04e54e54e8ea85c9bd35aff5482e064f5ee76b0 Mon Sep 17 00:00:00 2001 From: Gheorghe Pinzaru Date: Wed, 28 Oct 2020 10:56:14 +0300 Subject: [PATCH] Local notifications service (#2026) Also adds implementation for eth transactions notifications --- VERSION | 2 +- api/geth_backend.go | 87 ++++- appdatabase/migrations/bindata.go | 196 +++++++---- ...6_local_notifications_preferences.down.sql | 1 + ...016_local_notifications_preferences.up.sql | 7 + mobile/status.go | 12 + multiaccounts/accounts/database.go | 16 + multiaccounts/accounts/database_test.go | 18 + node/get_status_node.go | 12 + params/config.go | 8 + services/local-notifications/api.go | 35 ++ services/local-notifications/core.go | 333 ++++++++++++++++++ services/local-notifications/core_test.go | 135 +++++++ services/local-notifications/database.go | 50 +++ services/local-notifications/database_test.go | 67 ++++ services/wallet/database.go | 17 + services/wallet/database_test.go | 4 +- services/wallet/downloader.go | 1 + services/wallet/service.go | 13 +- services/wallet/transfer_view.go | 7 + services/wallet/transfers_query.go | 12 +- signal/events_pn.go | 10 + 22 files changed, 969 insertions(+), 74 deletions(-) create mode 100644 appdatabase/migrations/sql/0016_local_notifications_preferences.down.sql create mode 100644 appdatabase/migrations/sql/0016_local_notifications_preferences.up.sql create mode 100644 services/local-notifications/api.go create mode 100644 services/local-notifications/core.go create mode 100644 services/local-notifications/core_test.go create mode 100644 services/local-notifications/database.go create mode 100644 services/local-notifications/database_test.go create mode 100644 signal/events_pn.go diff --git a/VERSION b/VERSION index 06dfea5e6..b04cf74c6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.62.15 +0.62.16 diff --git a/api/geth_backend.go b/api/geth_backend.go index bed1f03f9..61c49a542 100644 --- a/api/geth_backend.go +++ b/api/geth_backend.go @@ -34,6 +34,7 @@ import ( "github.com/status-im/status-go/rpc" accountssvc "github.com/status-im/status-go/services/accounts" "github.com/status-im/status-go/services/browsers" + localnotifications "github.com/status-im/status-go/services/local-notifications" "github.com/status-im/status-go/services/mailservers" "github.com/status-im/status-go/services/permissions" "github.com/status-im/status-go/services/personal" @@ -96,6 +97,7 @@ func NewGethStatusBackend() *GethStatusBackend { transactor := transactions.NewTransactor() personalAPI := personal.NewAPI() rpcFilters := rpcfilters.New(statusNode) + return &GethStatusBackend{ statusNode: statusNode, accountManager: accountManager, @@ -339,10 +341,12 @@ func (b *GethStatusBackend) startNodeWithAccount(acc multiaccounts.Account, pass WatchAddresses: watchAddrs, MainAccount: walletAddr, } + err = b.StartNode(conf) if err != nil { return err } + err = b.SelectAccount(login) if err != nil { return err @@ -351,6 +355,7 @@ func (b *GethStatusBackend) startNodeWithAccount(acc multiaccounts.Account, pass if err != nil { return err } + return nil } @@ -514,6 +519,12 @@ func (b *GethStatusBackend) walletService(network uint64, accountsFeed *event.Fe } } +func (b *GethStatusBackend) localNotificationsService(network uint64) gethnode.ServiceConstructor { + return func(*gethnode.ServiceContext) (gethnode.Service, error) { + return localnotifications.NewService(b.appDB, network), nil + } +} + func (b *GethStatusBackend) startNode(config *params.NodeConfig) (err error) { defer func() { if r := recover(); r != nil { @@ -542,6 +553,7 @@ func (b *GethStatusBackend) startNode(config *params.NodeConfig) (err error) { services = appendIf(config.PermissionsConfig.Enabled, services, b.permissionsService()) services = appendIf(config.MailserversConfig.Enabled, services, b.mailserversService()) services = appendIf(config.WalletConfig.Enabled, services, b.walletService(config.NetworkID, accountsFeed)) + services = appendIf(config.LocalNotificationsConfig.Enabled, services, b.localNotificationsService(config.NetworkID)) manager := b.accountManager.GetManager() if manager == nil { @@ -889,17 +901,27 @@ func (b *GethStatusBackend) AppStateChange(state string) { } } } else if s == appStateBackground && !b.forceStopWallet { + localNotifications, err := b.statusNode.LocalNotificationsService() + if err != nil { + b.log.Error("Retrieving of local notifications service failed on app state change", "error", err) + } + wallet, err := b.statusNode.WalletService() if err != nil { b.log.Error("Retrieving of wallet service failed on app state change to background", "error", err) return } - err = wallet.Stop() - if err != nil { - b.log.Error("Wallet service stop failed on app state change to background", "error", err) - return + + if !localNotifications.IsWatchingWallet() { + err = wallet.Stop() + + if err != nil { + b.log.Error("Wallet service stop failed on app state change to background", "error", err) + return + } } } + // TODO: put node in low-power mode if the app is in background (or inactive) // and normal mode if the app is in foreground. } @@ -948,6 +970,62 @@ func (b *GethStatusBackend) StartWallet() error { return nil } +func (b *GethStatusBackend) StopLocalNotifications() error { + localPN, err := b.statusNode.LocalNotificationsService() + if err != nil { + b.log.Error("Retrieving of LocalNotifications service failed on StopLocalNotifications", "error", err) + return nil + } + + if localPN.IsStarted() { + err = localPN.Stop() + if err != nil { + b.log.Error("LocalNotifications service stop failed on StopLocalNotifications", "error", err) + return nil + } + } + + return nil +} + +func (b *GethStatusBackend) StartLocalNotifications() error { + localPN, err := b.statusNode.LocalNotificationsService() + + if err != nil { + b.log.Error("Retrieving of local notifications service failed on StartLocalNotifications", "error", err) + return nil + } + + wallet, err := b.statusNode.WalletService() + if err != nil { + b.log.Error("Retrieving of wallet service failed on StartLocalNotifications", "error", err) + return nil + } + + if !wallet.IsStarted() { + b.log.Error("Can't start local notifications service without wallet service") + return nil + } + + if !localPN.IsStarted() { + err = localPN.Start(b.statusNode.Server()) + + if err != nil { + b.log.Error("LocalNotifications service start failed on StartLocalNotifications", "error", err) + return nil + } + } + + err = localPN.SubscribeWallet(wallet.GetFeed()) + + if err != nil { + b.log.Error("LocalNotifications service could not subscribe to wallet on StartLocalNotifications", "error", err) + return nil + } + + return nil +} + // Logout clears whisper identities. func (b *GethStatusBackend) Logout() error { b.mu.Lock() @@ -1118,6 +1196,7 @@ func (b *GethStatusBackend) startWallet() error { } watchAddresses := b.accountManager.WatchAddresses() + mainAccountAddress, err := b.accountManager.MainAccountAddress() if err != nil { return err diff --git a/appdatabase/migrations/bindata.go b/appdatabase/migrations/bindata.go index 4d2248f0f..9360f53d9 100644 --- a/appdatabase/migrations/bindata.go +++ b/appdatabase/migrations/bindata.go @@ -27,6 +27,8 @@ // 0014_add_use_mailservers.up.sql (111B) // 0015_link_previews.down.sql (0) // 0015_link_previews.up.sql (203B) +// 0016_local_notifications_preferences.down.sql (43B) +// 0016_local_notifications_preferences.up.sql (213B) // doc.go (74B) package migrations @@ -47,7 +49,7 @@ import ( func bindataRead(data []byte, name string) ([]byte, error) { gz, err := gzip.NewReader(bytes.NewBuffer(data)) if err != nil { - return nil, fmt.Errorf("read %q: %w", name, err) + return nil, fmt.Errorf("read %q: %v", name, err) } var buf bytes.Buffer @@ -55,7 +57,7 @@ func bindataRead(data []byte, name string) ([]byte, error) { clErr := gz.Close() if err != nil { - return nil, fmt.Errorf("read %q: %w", name, err) + return nil, fmt.Errorf("read %q: %v", name, err) } if clErr != nil { return nil, err @@ -111,7 +113,7 @@ func _0001_appDownSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "0001_app.down.sql", size: 356, mode: os.FileMode(0644), modTime: time.Unix(1601293035, 0)} + info := bindataFileInfo{name: "0001_app.down.sql", size: 356, mode: os.FileMode(0644), modTime: time.Unix(1593853962, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xb5, 0x25, 0xa0, 0xf8, 0x7d, 0x2d, 0xd, 0xcf, 0x18, 0xe4, 0x73, 0xc3, 0x95, 0xf5, 0x24, 0x20, 0xa9, 0xe6, 0x9e, 0x1d, 0x93, 0xe5, 0xc5, 0xad, 0x93, 0x8f, 0x5e, 0x40, 0xb5, 0x30, 0xaa, 0x25}} return a, nil } @@ -131,7 +133,7 @@ func _0001_appUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "0001_app.up.sql", size: 2967, mode: os.FileMode(0644), modTime: time.Unix(1601293035, 0)} + info := bindataFileInfo{name: "0001_app.up.sql", size: 2967, mode: os.FileMode(0644), modTime: time.Unix(1593853962, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xf7, 0x3a, 0xa7, 0xf2, 0x8f, 0xfa, 0x82, 0x7c, 0xc5, 0x49, 0xac, 0xac, 0xf, 0xc, 0x77, 0xe2, 0xba, 0xe8, 0x4d, 0xe, 0x6f, 0x5d, 0x2c, 0x2c, 0x18, 0x80, 0xc2, 0x1d, 0xe, 0x25, 0xe, 0x18}} return a, nil } @@ -151,7 +153,7 @@ func _0002_tokensDownSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "0002_tokens.down.sql", size: 19, mode: os.FileMode(0644), modTime: time.Unix(1601293035, 0)} + info := bindataFileInfo{name: "0002_tokens.down.sql", size: 19, mode: os.FileMode(0644), modTime: time.Unix(1593853962, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xd1, 0x31, 0x2, 0xcc, 0x2f, 0x38, 0x90, 0xf7, 0x58, 0x37, 0x47, 0xf4, 0x18, 0xf7, 0x72, 0x74, 0x67, 0x14, 0x7e, 0xf3, 0xb1, 0xd6, 0x5f, 0xb0, 0xd5, 0xe7, 0x91, 0xf4, 0x26, 0x77, 0x8e, 0x68}} return a, nil } @@ -171,7 +173,7 @@ func _0002_tokensUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "0002_tokens.up.sql", size: 248, mode: os.FileMode(0644), modTime: time.Unix(1601293035, 0)} + info := bindataFileInfo{name: "0002_tokens.up.sql", size: 248, mode: os.FileMode(0644), modTime: time.Unix(1593853962, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xcc, 0xd6, 0xde, 0xd3, 0x7b, 0xee, 0x92, 0x11, 0x38, 0xa4, 0xeb, 0x84, 0xca, 0xcb, 0x37, 0x75, 0x5, 0x77, 0x7f, 0x14, 0x39, 0xee, 0xa1, 0x8b, 0xd4, 0x5c, 0x6e, 0x55, 0x6, 0x50, 0x16, 0xd4}} return a, nil } @@ -191,7 +193,7 @@ func _0003_settingsDownSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "0003_settings.down.sql", size: 118, mode: os.FileMode(0644), modTime: time.Unix(1601293035, 0)} + info := bindataFileInfo{name: "0003_settings.down.sql", size: 118, mode: os.FileMode(0644), modTime: time.Unix(1593853962, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xe5, 0xa6, 0xf5, 0xc0, 0x60, 0x64, 0x77, 0xe2, 0xe7, 0x3c, 0x9b, 0xb1, 0x52, 0xa9, 0x95, 0x16, 0xf8, 0x60, 0x2f, 0xa5, 0xeb, 0x46, 0xb9, 0xb9, 0x8f, 0x4c, 0xf4, 0xfd, 0xbb, 0xe7, 0xe5, 0xe5}} return a, nil } @@ -211,7 +213,7 @@ func _0003_settingsUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "0003_settings.up.sql", size: 1311, mode: os.FileMode(0644), modTime: time.Unix(1601293035, 0)} + info := bindataFileInfo{name: "0003_settings.up.sql", size: 1311, mode: os.FileMode(0644), modTime: time.Unix(1593853962, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xea, 0x35, 0x0, 0xeb, 0xe2, 0x33, 0x68, 0xb9, 0xf4, 0xf6, 0x8e, 0x9e, 0x10, 0xe9, 0x58, 0x68, 0x28, 0xb, 0xcd, 0xec, 0x74, 0x71, 0xa7, 0x9a, 0x5a, 0x77, 0x59, 0xb1, 0x13, 0x1c, 0xa1, 0x5b}} return a, nil } @@ -231,7 +233,7 @@ func _0004_pending_stickersDownSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "0004_pending_stickers.down.sql", size: 0, mode: os.FileMode(0644), modTime: time.Unix(1601293035, 0)} + info := bindataFileInfo{name: "0004_pending_stickers.down.sql", size: 0, mode: os.FileMode(0644), modTime: time.Unix(1593853962, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55}} return a, nil } @@ -251,7 +253,7 @@ func _0004_pending_stickersUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "0004_pending_stickers.up.sql", size: 61, mode: os.FileMode(0644), modTime: time.Unix(1602064144, 0)} + info := bindataFileInfo{name: "0004_pending_stickers.up.sql", size: 61, mode: os.FileMode(0644), modTime: time.Unix(1593853962, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x3c, 0xed, 0x25, 0xdf, 0x75, 0x2, 0x6c, 0xf0, 0xa2, 0xa8, 0x37, 0x62, 0x65, 0xad, 0xfd, 0x98, 0xa0, 0x9d, 0x63, 0x94, 0xdf, 0x6b, 0x46, 0xe0, 0x68, 0xec, 0x9c, 0x7f, 0x77, 0xdd, 0xb3, 0x6}} return a, nil } @@ -271,7 +273,7 @@ func _0005_waku_modeDownSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "0005_waku_mode.down.sql", size: 0, mode: os.FileMode(0644), modTime: time.Unix(1601293035, 0)} + info := bindataFileInfo{name: "0005_waku_mode.down.sql", size: 0, mode: os.FileMode(0644), modTime: time.Unix(1593853962, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55}} return a, nil } @@ -291,7 +293,7 @@ func _0005_waku_modeUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "0005_waku_mode.up.sql", size: 146, mode: os.FileMode(0644), modTime: time.Unix(1601293035, 0)} + info := bindataFileInfo{name: "0005_waku_mode.up.sql", size: 146, mode: os.FileMode(0644), modTime: time.Unix(1593853962, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xa6, 0x91, 0xc, 0xd7, 0x89, 0x61, 0x2e, 0x4c, 0x5a, 0xb6, 0x67, 0xd1, 0xc1, 0x42, 0x24, 0x38, 0xd6, 0x1b, 0x75, 0x41, 0x9c, 0x23, 0xb0, 0xca, 0x5c, 0xf1, 0x5c, 0xd0, 0x13, 0x92, 0x3e, 0xe1}} return a, nil } @@ -311,7 +313,7 @@ func _0006_appearanceUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "0006_appearance.up.sql", size: 67, mode: os.FileMode(0644), modTime: time.Unix(1601293035, 0)} + info := bindataFileInfo{name: "0006_appearance.up.sql", size: 67, mode: os.FileMode(0644), modTime: time.Unix(1593853962, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xae, 0x6, 0x25, 0x6c, 0xe4, 0x9d, 0xa7, 0x72, 0xe8, 0xbc, 0xe4, 0x1f, 0x1e, 0x2d, 0x7c, 0xb7, 0xf6, 0xa3, 0xec, 0x3b, 0x4e, 0x93, 0x2e, 0xa4, 0xec, 0x6f, 0xe5, 0x95, 0x94, 0xe8, 0x4, 0xfb}} return a, nil } @@ -331,7 +333,7 @@ func _0007_enable_waku_defaultUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "0007_enable_waku_default.up.sql", size: 38, mode: os.FileMode(0644), modTime: time.Unix(1601293035, 0)} + info := bindataFileInfo{name: "0007_enable_waku_default.up.sql", size: 38, mode: os.FileMode(0644), modTime: time.Unix(1593853962, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xd4, 0x42, 0xb6, 0xe5, 0x48, 0x41, 0xeb, 0xc0, 0x7e, 0x3b, 0xe6, 0x8e, 0x96, 0x33, 0x20, 0x92, 0x24, 0x5a, 0x60, 0xfa, 0xa0, 0x3, 0x5e, 0x76, 0x4b, 0x89, 0xaa, 0x37, 0x66, 0xbc, 0x26, 0x11}} return a, nil } @@ -351,7 +353,7 @@ func _0008_add_push_notificationsUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "0008_add_push_notifications.up.sql", size: 349, mode: os.FileMode(0644), modTime: time.Unix(1602064187, 0)} + info := bindataFileInfo{name: "0008_add_push_notifications.up.sql", size: 349, mode: os.FileMode(0644), modTime: time.Unix(1598257438, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x5a, 0x0, 0xbf, 0xd0, 0xdd, 0xcd, 0x73, 0xe0, 0x7c, 0x56, 0xef, 0xdc, 0x57, 0x61, 0x94, 0x64, 0x70, 0xb9, 0xfa, 0xa1, 0x2a, 0x36, 0xc, 0x2f, 0xf8, 0x95, 0xa, 0x57, 0x3e, 0x7a, 0xd7, 0x12}} return a, nil } @@ -371,7 +373,7 @@ func _0009_enable_sending_push_notificationsDownSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "0009_enable_sending_push_notifications.down.sql", size: 49, mode: os.FileMode(0644), modTime: time.Unix(1601293035, 0)} + info := bindataFileInfo{name: "0009_enable_sending_push_notifications.down.sql", size: 49, mode: os.FileMode(0644), modTime: time.Unix(1600853026, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xe9, 0xae, 0x1b, 0x41, 0xcb, 0x9c, 0x2c, 0x93, 0xc6, 0x2a, 0x77, 0x3, 0xb9, 0x51, 0xe0, 0x68, 0x68, 0x0, 0xf7, 0x5b, 0xb3, 0x1e, 0x94, 0x44, 0xba, 0x9c, 0xd0, 0x3b, 0x80, 0x21, 0x6f, 0xb5}} return a, nil } @@ -391,7 +393,7 @@ func _0009_enable_sending_push_notificationsUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "0009_enable_sending_push_notifications.up.sql", size: 49, mode: os.FileMode(0644), modTime: time.Unix(1601293035, 0)} + info := bindataFileInfo{name: "0009_enable_sending_push_notifications.up.sql", size: 49, mode: os.FileMode(0644), modTime: time.Unix(1600853026, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x1b, 0x80, 0xe4, 0x9c, 0xc8, 0xb8, 0xd5, 0xef, 0xce, 0x74, 0x9b, 0x7b, 0xdd, 0xa, 0x99, 0x1e, 0xef, 0x7f, 0xb8, 0x99, 0x84, 0x4, 0x0, 0x6b, 0x1d, 0x2c, 0xa, 0xf8, 0x2c, 0x4f, 0xb5, 0x44}} return a, nil } @@ -411,7 +413,7 @@ func _0010_add_block_mentionsDownSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "0010_add_block_mentions.down.sql", size: 83, mode: os.FileMode(0644), modTime: time.Unix(1601293035, 0)} + info := bindataFileInfo{name: "0010_add_block_mentions.down.sql", size: 83, mode: os.FileMode(0644), modTime: time.Unix(1600853026, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x6d, 0x9e, 0x27, 0x1e, 0xba, 0x9f, 0xca, 0xae, 0x98, 0x2e, 0x6e, 0xe3, 0xdd, 0xac, 0x73, 0x34, 0x4e, 0x69, 0x92, 0xb5, 0xf6, 0x9, 0xab, 0x50, 0x35, 0xd, 0xee, 0xeb, 0x3e, 0xcc, 0x7e, 0xce}} return a, nil } @@ -431,7 +433,7 @@ func _0010_add_block_mentionsUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "0010_add_block_mentions.up.sql", size: 89, mode: os.FileMode(0644), modTime: time.Unix(1601293035, 0)} + info := bindataFileInfo{name: "0010_add_block_mentions.up.sql", size: 89, mode: os.FileMode(0644), modTime: time.Unix(1600853026, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xd7, 0x23, 0x85, 0xa2, 0xb5, 0xb6, 0xb4, 0x3f, 0xdc, 0x4e, 0xff, 0xe2, 0x6b, 0x66, 0x68, 0x5e, 0xb2, 0xb4, 0x14, 0xb2, 0x1b, 0x4d, 0xb1, 0xce, 0xf7, 0x6, 0x58, 0xa7, 0xaf, 0x93, 0x3f, 0x25}} return a, nil } @@ -451,7 +453,7 @@ func _0011_allow_webview_permission_requestsDownSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "0011_allow_webview_permission_requests.down.sql", size: 0, mode: os.FileMode(0644), modTime: time.Unix(1601293035, 0)} + info := bindataFileInfo{name: "0011_allow_webview_permission_requests.down.sql", size: 0, mode: os.FileMode(0644), modTime: time.Unix(1600853026, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55}} return a, nil } @@ -471,7 +473,7 @@ func _0011_allow_webview_permission_requestsUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "0011_allow_webview_permission_requests.up.sql", size: 88, mode: os.FileMode(0644), modTime: time.Unix(1601293035, 0)} + info := bindataFileInfo{name: "0011_allow_webview_permission_requests.up.sql", size: 88, mode: os.FileMode(0644), modTime: time.Unix(1600853026, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x43, 0x5f, 0x22, 0x4c, 0x98, 0x1d, 0xc6, 0xf4, 0x89, 0xaf, 0xf4, 0x44, 0xba, 0xf8, 0x28, 0xa7, 0xb5, 0xb9, 0xf0, 0xf2, 0xcb, 0x5, 0x59, 0x7a, 0xc, 0xdf, 0xd3, 0x38, 0xa4, 0xb8, 0x98, 0xc2}} return a, nil } @@ -491,7 +493,7 @@ func _0012_pending_transactionsDownSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "0012_pending_transactions.down.sql", size: 33, mode: os.FileMode(0644), modTime: time.Unix(1601293035, 0)} + info := bindataFileInfo{name: "0012_pending_transactions.down.sql", size: 33, mode: os.FileMode(0644), modTime: time.Unix(1600853026, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x7e, 0x41, 0xfe, 0x5c, 0xd8, 0xc3, 0x29, 0xfd, 0x31, 0x78, 0x99, 0x7a, 0xeb, 0x17, 0x62, 0x88, 0x41, 0xb3, 0xe7, 0xb5, 0x5, 0x0, 0x90, 0xa1, 0x7, 0x1a, 0x23, 0x88, 0x81, 0xba, 0x56, 0x9d}} return a, nil } @@ -511,7 +513,7 @@ func _0012_pending_transactionsUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "0012_pending_transactions.up.sql", size: 321, mode: os.FileMode(0644), modTime: time.Unix(1601293035, 0)} + info := bindataFileInfo{name: "0012_pending_transactions.up.sql", size: 321, mode: os.FileMode(0644), modTime: time.Unix(1600853026, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xd, 0x17, 0xff, 0xd7, 0xa7, 0x49, 0x1e, 0x7b, 0x34, 0x63, 0x7c, 0x53, 0xaa, 0x6b, 0x2d, 0xc8, 0xe0, 0x82, 0x21, 0x90, 0x3a, 0x94, 0xf1, 0xa6, 0xe4, 0x70, 0xe5, 0x85, 0x1a, 0x48, 0x25, 0xb}} return a, nil } @@ -531,7 +533,7 @@ func _0013_favouritesDownSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "0013_favourites.down.sql", size: 23, mode: os.FileMode(0644), modTime: time.Unix(1601293035, 0)} + info := bindataFileInfo{name: "0013_favourites.down.sql", size: 23, mode: os.FileMode(0644), modTime: time.Unix(1600853026, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x32, 0xf8, 0x55, 0x13, 0x4f, 0x4a, 0x19, 0x83, 0x9c, 0xda, 0x34, 0xb8, 0x3, 0x54, 0x82, 0x1e, 0x99, 0x36, 0x6b, 0x42, 0x3, 0xf6, 0x43, 0xde, 0xe6, 0x32, 0xb6, 0xdf, 0xe2, 0x59, 0x8c, 0x84}} return a, nil } @@ -551,7 +553,7 @@ func _0013_favouritesUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "0013_favourites.up.sql", size: 132, mode: os.FileMode(0644), modTime: time.Unix(1601293035, 0)} + info := bindataFileInfo{name: "0013_favourites.up.sql", size: 132, mode: os.FileMode(0644), modTime: time.Unix(1600853026, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xbe, 0x1, 0x27, 0x38, 0x76, 0xf5, 0xcb, 0x61, 0xda, 0x5b, 0xce, 0xd9, 0x8b, 0x18, 0x77, 0x61, 0x84, 0xe7, 0x22, 0xe2, 0x13, 0x99, 0xab, 0x32, 0xbc, 0xbe, 0xed, 0x1f, 0x2f, 0xb0, 0xe4, 0x8d}} return a, nil } @@ -571,7 +573,7 @@ func _0014_add_use_mailserversDownSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "0014_add_use_mailservers.down.sql", size: 0, mode: os.FileMode(0644), modTime: time.Unix(1603194874, 0)} + info := bindataFileInfo{name: "0014_add_use_mailservers.down.sql", size: 0, mode: os.FileMode(0644), modTime: time.Unix(1603868977, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55}} return a, nil } @@ -591,7 +593,7 @@ func _0014_add_use_mailserversUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "0014_add_use_mailservers.up.sql", size: 111, mode: os.FileMode(0644), modTime: time.Unix(1603194874, 0)} + info := bindataFileInfo{name: "0014_add_use_mailservers.up.sql", size: 111, mode: os.FileMode(0644), modTime: time.Unix(1603868977, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xc9, 0xba, 0x65, 0xbf, 0x1b, 0xc9, 0x6d, 0x45, 0xf2, 0xf5, 0x30, 0x7c, 0xc1, 0xde, 0xb8, 0xe3, 0x3f, 0xa9, 0x2f, 0x9f, 0xea, 0x1, 0x29, 0x29, 0x65, 0xe7, 0x38, 0xab, 0xa4, 0x62, 0xf, 0xd0}} return a, nil } @@ -611,7 +613,7 @@ func _0015_link_previewsDownSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "0015_link_previews.down.sql", size: 0, mode: os.FileMode(0644), modTime: time.Unix(1603816327, 0)} + info := bindataFileInfo{name: "0015_link_previews.down.sql", size: 0, mode: os.FileMode(0644), modTime: time.Unix(1603869530, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55}} return a, nil } @@ -631,11 +633,51 @@ func _0015_link_previewsUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "0015_link_previews.up.sql", size: 203, mode: os.FileMode(0644), modTime: time.Unix(1603816369, 0)} + info := bindataFileInfo{name: "0015_link_previews.up.sql", size: 203, mode: os.FileMode(0644), modTime: time.Unix(1603869530, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xb1, 0xf7, 0x38, 0x25, 0xa6, 0xfc, 0x6b, 0x9, 0xe4, 0xd9, 0xbf, 0x58, 0x7b, 0x80, 0xd8, 0x48, 0x63, 0xde, 0xa5, 0x5e, 0x30, 0xa3, 0xeb, 0x68, 0x8e, 0x6a, 0x9f, 0xfd, 0xf4, 0x46, 0x41, 0x34}} return a, nil } +var __0016_local_notifications_preferencesDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\xc8\xc9\x4f\x4e\xcc\x89\xcf\xcb\x2f\xc9\x4c\xcb\x4c\x4e\x2c\xc9\xcc\xcf\x2b\x8e\x2f\x28\x4a\x4d\x4b\x2d\x4a\xcd\x4b\x4e\x2d\xb6\x06\x04\x00\x00\xff\xff\xf0\xdb\xee\xaa\x2b\x00\x00\x00") + +func _0016_local_notifications_preferencesDownSqlBytes() ([]byte, error) { + return bindataRead( + __0016_local_notifications_preferencesDownSql, + "0016_local_notifications_preferences.down.sql", + ) +} + +func _0016_local_notifications_preferencesDownSql() (*asset, error) { + bytes, err := _0016_local_notifications_preferencesDownSqlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "0016_local_notifications_preferences.down.sql", size: 43, mode: os.FileMode(0644), modTime: time.Unix(1603869534, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xe0, 0x50, 0xc7, 0xdd, 0x53, 0x9c, 0x5d, 0x1e, 0xb5, 0x71, 0x25, 0x50, 0x58, 0xcf, 0x6d, 0xbe, 0x5a, 0x8, 0x12, 0xc9, 0x13, 0xd, 0x9a, 0x3d, 0x4b, 0x7a, 0x2f, 0x1b, 0xe5, 0x23, 0x52, 0x78}} + return a, nil +} + +var __0016_local_notifications_preferencesUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x54\x8d\xc1\x6a\x84\x30\x14\x45\xf7\x7e\xc5\x5d\x2a\xf8\x07\x5d\x45\x4d\x51\x9a\x9a\x12\x62\xc5\x95\xa4\xf1\x09\x81\x10\x4b\x22\x7e\xff\x30\x3a\xc3\x30\xdb\x7b\x2e\xe7\xd4\x8a\x33\xcd\xa1\x59\x25\x38\xfc\x66\x8d\x9f\xc3\xb6\xbb\xd5\x59\xb3\xbb\x2d\xa4\xf9\x3f\xd2\x4a\x91\x82\xa5\x84\x3c\x03\x80\x44\xf1\x70\x96\xf0\xcb\x54\xdd\x32\x85\x5e\x6a\xf4\x83\x10\xe5\x49\xe9\xa0\xb0\x3f\xd9\x35\xb9\x85\xc2\x5d\x49\xf1\x7d\xa7\x60\xfe\x3c\x2d\xa8\xa4\x14\x9c\xf5\x68\xf8\x27\x1b\x84\xc6\x6a\x7c\xa2\xeb\xf2\xa3\xba\x6f\xa6\x26\x7c\xf1\x29\x7f\x74\xcb\xb3\x50\xbe\xa4\x45\x56\x60\xec\x74\x2b\x07\x0d\x25\xc7\xae\xf9\xb8\x05\x00\x00\xff\xff\xd8\x7b\x68\x58\xd5\x00\x00\x00") + +func _0016_local_notifications_preferencesUpSqlBytes() ([]byte, error) { + return bindataRead( + __0016_local_notifications_preferencesUpSql, + "0016_local_notifications_preferences.up.sql", + ) +} + +func _0016_local_notifications_preferencesUpSql() (*asset, error) { + bytes, err := _0016_local_notifications_preferencesUpSqlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "0016_local_notifications_preferences.up.sql", size: 213, mode: os.FileMode(0644), modTime: time.Unix(1603869534, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x8, 0xde, 0x2, 0xbe, 0xf2, 0x84, 0x7, 0x3e, 0x44, 0xe5, 0xc6, 0xc9, 0xb, 0x70, 0x9f, 0x68, 0x2c, 0x6, 0x99, 0xe, 0xdd, 0xbb, 0x20, 0x0, 0x6a, 0x48, 0x15, 0xee, 0xb, 0x76, 0x9f, 0xd8}} + return a, nil +} + var _docGo = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x2c\xc9\xb1\x0d\xc4\x20\x0c\x05\xd0\x9e\x29\xfe\x02\xd8\xfd\x6d\xe3\x4b\xac\x2f\x44\x82\x09\x78\x7f\xa5\x49\xfd\xa6\x1d\xdd\xe8\xd8\xcf\x55\x8a\x2a\xe3\x47\x1f\xbe\x2c\x1d\x8c\xfa\x6f\xe3\xb4\x34\xd4\xd9\x89\xbb\x71\x59\xb6\x18\x1b\x35\x20\xa2\x9f\x0a\x03\xa2\xe5\x0d\x00\x00\xff\xff\x60\xcd\x06\xbe\x4a\x00\x00\x00") func docGoBytes() ([]byte, error) { @@ -651,7 +693,7 @@ func docGo() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "doc.go", size: 74, mode: os.FileMode(0644), modTime: time.Unix(1601293035, 0)} + info := bindataFileInfo{name: "doc.go", size: 74, mode: os.FileMode(0644), modTime: time.Unix(1593853962, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xde, 0x7c, 0x28, 0xcd, 0x47, 0xf2, 0xfa, 0x7c, 0x51, 0x2d, 0xd8, 0x38, 0xb, 0xb0, 0x34, 0x9d, 0x4c, 0x62, 0xa, 0x9e, 0x28, 0xc3, 0x31, 0x23, 0xd9, 0xbb, 0x89, 0x9f, 0xa0, 0x89, 0x1f, 0xe8}} return a, nil } @@ -747,38 +789,66 @@ func AssetNames() []string { // _bindata is a table, holding each asset generator, mapped to its name. var _bindata = map[string]func() (*asset, error){ - "0001_app.down.sql": _0001_appDownSql, - "0001_app.up.sql": _0001_appUpSql, - "0002_tokens.down.sql": _0002_tokensDownSql, - "0002_tokens.up.sql": _0002_tokensUpSql, - "0003_settings.down.sql": _0003_settingsDownSql, - "0003_settings.up.sql": _0003_settingsUpSql, - "0004_pending_stickers.down.sql": _0004_pending_stickersDownSql, - "0004_pending_stickers.up.sql": _0004_pending_stickersUpSql, - "0005_waku_mode.down.sql": _0005_waku_modeDownSql, - "0005_waku_mode.up.sql": _0005_waku_modeUpSql, - "0006_appearance.up.sql": _0006_appearanceUpSql, - "0007_enable_waku_default.up.sql": _0007_enable_waku_defaultUpSql, - "0008_add_push_notifications.up.sql": _0008_add_push_notificationsUpSql, - "0009_enable_sending_push_notifications.down.sql": _0009_enable_sending_push_notificationsDownSql, - "0009_enable_sending_push_notifications.up.sql": _0009_enable_sending_push_notificationsUpSql, - "0010_add_block_mentions.down.sql": _0010_add_block_mentionsDownSql, - "0010_add_block_mentions.up.sql": _0010_add_block_mentionsUpSql, - "0011_allow_webview_permission_requests.down.sql": _0011_allow_webview_permission_requestsDownSql, - "0011_allow_webview_permission_requests.up.sql": _0011_allow_webview_permission_requestsUpSql, - "0012_pending_transactions.down.sql": _0012_pending_transactionsDownSql, - "0012_pending_transactions.up.sql": _0012_pending_transactionsUpSql, - "0013_favourites.down.sql": _0013_favouritesDownSql, - "0013_favourites.up.sql": _0013_favouritesUpSql, - "0014_add_use_mailservers.down.sql": _0014_add_use_mailserversDownSql, - "0014_add_use_mailservers.up.sql": _0014_add_use_mailserversUpSql, - "0015_link_previews.down.sql": _0015_link_previewsDownSql, - "0015_link_previews.up.sql": _0015_link_previewsUpSql, - "doc.go": docGo, -} + "0001_app.down.sql": _0001_appDownSql, -// AssetDebug is true if the assets were built with the debug flag enabled. -const AssetDebug = false + "0001_app.up.sql": _0001_appUpSql, + + "0002_tokens.down.sql": _0002_tokensDownSql, + + "0002_tokens.up.sql": _0002_tokensUpSql, + + "0003_settings.down.sql": _0003_settingsDownSql, + + "0003_settings.up.sql": _0003_settingsUpSql, + + "0004_pending_stickers.down.sql": _0004_pending_stickersDownSql, + + "0004_pending_stickers.up.sql": _0004_pending_stickersUpSql, + + "0005_waku_mode.down.sql": _0005_waku_modeDownSql, + + "0005_waku_mode.up.sql": _0005_waku_modeUpSql, + + "0006_appearance.up.sql": _0006_appearanceUpSql, + + "0007_enable_waku_default.up.sql": _0007_enable_waku_defaultUpSql, + + "0008_add_push_notifications.up.sql": _0008_add_push_notificationsUpSql, + + "0009_enable_sending_push_notifications.down.sql": _0009_enable_sending_push_notificationsDownSql, + + "0009_enable_sending_push_notifications.up.sql": _0009_enable_sending_push_notificationsUpSql, + + "0010_add_block_mentions.down.sql": _0010_add_block_mentionsDownSql, + + "0010_add_block_mentions.up.sql": _0010_add_block_mentionsUpSql, + + "0011_allow_webview_permission_requests.down.sql": _0011_allow_webview_permission_requestsDownSql, + + "0011_allow_webview_permission_requests.up.sql": _0011_allow_webview_permission_requestsUpSql, + + "0012_pending_transactions.down.sql": _0012_pending_transactionsDownSql, + + "0012_pending_transactions.up.sql": _0012_pending_transactionsUpSql, + + "0013_favourites.down.sql": _0013_favouritesDownSql, + + "0013_favourites.up.sql": _0013_favouritesUpSql, + + "0014_add_use_mailservers.down.sql": _0014_add_use_mailserversDownSql, + + "0014_add_use_mailservers.up.sql": _0014_add_use_mailserversUpSql, + + "0015_link_previews.down.sql": _0015_link_previewsDownSql, + + "0015_link_previews.up.sql": _0015_link_previewsUpSql, + + "0016_local_notifications_preferences.down.sql": _0016_local_notifications_preferencesDownSql, + + "0016_local_notifications_preferences.up.sql": _0016_local_notifications_preferencesUpSql, + + "doc.go": docGo, +} // AssetDir returns the file names below a certain // directory embedded in the file by go-bindata. @@ -848,7 +918,9 @@ var _bintree = &bintree{nil, map[string]*bintree{ "0014_add_use_mailservers.up.sql": &bintree{_0014_add_use_mailserversUpSql, map[string]*bintree{}}, "0015_link_previews.down.sql": &bintree{_0015_link_previewsDownSql, map[string]*bintree{}}, "0015_link_previews.up.sql": &bintree{_0015_link_previewsUpSql, map[string]*bintree{}}, - "doc.go": &bintree{docGo, map[string]*bintree{}}, + "0016_local_notifications_preferences.down.sql": &bintree{_0016_local_notifications_preferencesDownSql, map[string]*bintree{}}, + "0016_local_notifications_preferences.up.sql": &bintree{_0016_local_notifications_preferencesUpSql, map[string]*bintree{}}, + "doc.go": &bintree{docGo, map[string]*bintree{}}, }} // RestoreAsset restores an asset under the given directory. diff --git a/appdatabase/migrations/sql/0016_local_notifications_preferences.down.sql b/appdatabase/migrations/sql/0016_local_notifications_preferences.down.sql new file mode 100644 index 000000000..6c022198c --- /dev/null +++ b/appdatabase/migrations/sql/0016_local_notifications_preferences.down.sql @@ -0,0 +1 @@ +DROP TABLE local_notifications_preferences; \ No newline at end of file diff --git a/appdatabase/migrations/sql/0016_local_notifications_preferences.up.sql b/appdatabase/migrations/sql/0016_local_notifications_preferences.up.sql new file mode 100644 index 000000000..b7067a20e --- /dev/null +++ b/appdatabase/migrations/sql/0016_local_notifications_preferences.up.sql @@ -0,0 +1,7 @@ +CREATE TABLE local_notifications_preferences ( + service VARCHAR, + event VARCHAR, + identifier VARCHAR, + enabled BOOLEAN DEFAULT false, + PRIMARY KEY(service,event,identifier) +) WITHOUT ROWID; \ No newline at end of file diff --git a/mobile/status.go b/mobile/status.go index 0756e9a4c..fbcdf49be 100644 --- a/mobile/status.go +++ b/mobile/status.go @@ -636,6 +636,18 @@ func StartWallet() string { return makeJSONResponse(err) } +// StartLocalNotifications +func StartLocalNotifications() string { + err := statusBackend.StartLocalNotifications() + return makeJSONResponse(err) +} + +// StopLocalNotifications +func StopLocalNotifications() string { + err := statusBackend.StopLocalNotifications() + return makeJSONResponse(err) +} + // SetMobileSignalHandler setup geth callback to notify about new signal // used for gomobile builds func SetMobileSignalHandler(handler SignalHandler) { diff --git a/multiaccounts/accounts/database.go b/multiaccounts/accounts/database.go index e3f1d9d7a..87ad15ffb 100644 --- a/multiaccounts/accounts/database.go +++ b/multiaccounts/accounts/database.go @@ -436,6 +436,22 @@ func (db *Database) GetAccounts() ([]Account, error) { return accounts, nil } +func (db *Database) GetAccountByAddress(address types.Address) (rst *Account, err error) { + row := db.db.QueryRow("SELECT address, wallet, chat, type, storage, pubkey, path, name, color FROM accounts WHERE address = ? COLLATE NOCASE", address) + + acc := &Account{} + pubkey := []byte{} + err = row.Scan( + &acc.Address, &acc.Wallet, &acc.Chat, &acc.Type, &acc.Storage, + &pubkey, &acc.Path, &acc.Name, &acc.Color) + + if err != nil { + return nil, err + } + acc.PublicKey = pubkey + return acc, nil +} + func (db *Database) SaveAccounts(accounts []Account) (err error) { var ( tx *sql.Tx diff --git a/multiaccounts/accounts/database_test.go b/multiaccounts/accounts/database_test.go index c012f575d..1ae4787be 100644 --- a/multiaccounts/accounts/database_test.go +++ b/multiaccounts/accounts/database_test.go @@ -206,6 +206,24 @@ func TestGetAccounts(t *testing.T) { require.Equal(t, accounts, rst) } +func TestGetAccountByAddress(t *testing.T) { + db, stop := setupTestDB(t) + defer stop() + address := types.Address{0x01} + account := Account{Address: address, Chat: true, Wallet: true} + dilute := []Account{ + {Address: types.Address{0x02}, PublicKey: types.HexBytes{0x01, 0x02}}, + {Address: types.Address{0x03}, PublicKey: types.HexBytes{0x02, 0x03}}, + } + + accounts := append(dilute, account) + + require.NoError(t, db.SaveAccounts(accounts)) + rst, err := db.GetAccountByAddress(address) + require.NoError(t, err) + require.Equal(t, &account, rst) +} + func TestAddressExists(t *testing.T) { db, stop := setupTestDB(t) defer stop() diff --git a/node/get_status_node.go b/node/get_status_node.go index a350dcaa7..1a236d7af 100644 --- a/node/get_status_node.go +++ b/node/get_status_node.go @@ -33,6 +33,7 @@ import ( "github.com/status-im/status-go/peers" "github.com/status-im/status-go/rpc" "github.com/status-im/status-go/services/browsers" + localnotifications "github.com/status-im/status-go/services/local-notifications" "github.com/status-im/status-go/services/peer" "github.com/status-im/status-go/services/permissions" "github.com/status-im/status-go/services/shhext" @@ -671,6 +672,17 @@ func (n *StatusNode) WalletService() (s *wallet.Service, err error) { return } +// LocalNotificationsService returns localnotifications.Service instance if it was started. +func (n *StatusNode) LocalNotificationsService() (s *localnotifications.Service, err error) { + n.mu.RLock() + defer n.mu.RUnlock() + err = n.gethService(&s) + if err == node.ErrServiceUnknown { + err = ErrServiceUnknown + } + return +} + // BrowsersService returns browsers.Service instance if it was started. func (n *StatusNode) BrowsersService() (s *browsers.Service, err error) { n.mu.RLock() diff --git a/params/config.go b/params/config.go index e76c3b62c..27a60b862 100644 --- a/params/config.go +++ b/params/config.go @@ -436,6 +436,9 @@ type NodeConfig struct { // WalletConfig extra configuration for wallet.Service. WalletConfig WalletConfig + // WalleLocalNotificationsConfig extra configuration for localnotifications.Service. + LocalNotificationsConfig LocalNotificationsConfig + // BrowsersConfig extra configuration for browsers.Service. BrowsersConfig BrowsersConfig @@ -469,6 +472,11 @@ type WalletConfig struct { Enabled bool } +// LocalNotificationsConfig extra configuration for localnotifications.Service. +type LocalNotificationsConfig struct { + Enabled bool +} + // BrowsersConfig extra configuration for browsers.Service. type BrowsersConfig struct { Enabled bool diff --git a/services/local-notifications/api.go b/services/local-notifications/api.go new file mode 100644 index 000000000..3c5e226e3 --- /dev/null +++ b/services/local-notifications/api.go @@ -0,0 +1,35 @@ +package localnotifications + +import ( + "context" + + "github.com/ethereum/go-ethereum/log" +) + +func NewAPI(s *Service) *API { + return &API{s} +} + +type API struct { + s *Service +} + +func (api *API) NotificationPreferences(ctx context.Context) ([]NotificationPreference, error) { + return api.s.db.GetPreferences() +} + +func (api *API) SwitchWalletNotifications(ctx context.Context, preference bool) error { + log.Debug("Switch Transaction Notification") + err := api.s.db.ChangeWalletPreference(preference) + if err != nil { + return err + } + + if preference { + api.s.StartWalletWatcher() + } else { + api.s.StopWalletWatcher() + } + + return nil +} diff --git a/services/local-notifications/core.go b/services/local-notifications/core.go new file mode 100644 index 000000000..15f475799 --- /dev/null +++ b/services/local-notifications/core.go @@ -0,0 +1,333 @@ +package localnotifications + +import ( + "database/sql" + "math/big" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/rpc" + "github.com/status-im/status-go/eth-node/types" + "github.com/status-im/status-go/multiaccounts/accounts" + "github.com/status-im/status-go/services/wallet" + "github.com/status-im/status-go/signal" +) + +type PushCategory string + +type transactionState string + +const walletDeeplinkPrefix = "status-im://wallet/" + +const ( + failed transactionState = "failed" + inbound transactionState = "inbound" + outbound transactionState = "outbound" +) + +type notificationBody struct { + State transactionState `json:"state"` + From common.Address `json:"from"` + To common.Address `json:"to"` + FromAccount *accounts.Account `json:"fromAccount,omitempty"` + ToAccount *accounts.Account `json:"toAccount,omitempty"` + Value *hexutil.Big `json:"value"` + ERC20 bool `json:"erc20"` + Contract common.Address `json:"contract"` + Network uint64 `json:"network"` +} + +type Notification struct { + ID common.Hash `json:"id"` + Platform float32 `json:"platform,omitempty"` + Body notificationBody `json:"body"` + Category PushCategory `json:"category,omitempty"` + Deeplink string `json:"deepLink,omitempty"` + Image string `json:"imageUrl,omitempty"` + IsScheduled bool `json:"isScheduled,omitempty"` + ScheduledTime string `json:"scheduleTime,omitempty"` +} + +// TransactionEvent - structure used to pass messages from wallet to bus +type TransactionEvent struct { + Type string `json:"type"` + BlockNumber *big.Int `json:"block-number"` + Accounts []common.Address `json:"accounts"` + NewTransactionsPerAccount map[common.Address]int `json:"new-transactions"` + ERC20 bool `json:"erc20"` +} + +// MessageEvent - structure used to pass messages from chat to bus +type MessageEvent struct{} + +// CustomEvent - structure used to pass custom user set messages to bus +type CustomEvent struct{} + +type transmitter struct { + publisher *event.Feed + + wg sync.WaitGroup + quit chan struct{} +} + +// Service keeps the state of message bus +type Service struct { + started bool + transmitter *transmitter + walletTransmitter *transmitter + db *Database + walletDB *wallet.Database + accountsDB *accounts.Database +} + +func NewService(appDB *sql.DB, network uint64) *Service { + db := NewDB(appDB, network) + walletDB := wallet.NewDB(appDB, network) + accountsDB := accounts.NewDB(appDB) + trans := &transmitter{} + walletTrans := &transmitter{} + + return &Service{ + db: db, + walletDB: walletDB, + accountsDB: accountsDB, + transmitter: trans, + walletTransmitter: walletTrans, + } +} + +func pushMessage(notification *Notification) { + log.Info("Pushing a new push notification", "info", notification) + signal.SendLocalNotifications(notification) +} + +func (s *Service) buildTransactionNotification(rawTransfer wallet.Transfer) *Notification { + log.Info("Handled a new transfer in buildTransactionNotification", "info", rawTransfer) + + var deeplink string + var state transactionState + transfer := wallet.CastToTransferView(rawTransfer) + + switch { + case transfer.TxStatus == hexutil.Uint64(0): + state = failed + case transfer.Address == transfer.To: + state = inbound + default: + state = outbound + } + + from, err := s.accountsDB.GetAccountByAddress(types.Address(transfer.From)) + + if err != nil { + log.Debug("Could not select From account by address", "error", err) + } + + to, err := s.accountsDB.GetAccountByAddress(types.Address(transfer.To)) + + if err != nil { + log.Debug("Could not select To account by address", "error", err) + } + + if from != nil { + deeplink = walletDeeplinkPrefix + from.Address.String() + } else if to != nil { + deeplink = walletDeeplinkPrefix + to.Address.String() + } + + body := notificationBody{ + State: state, + From: transfer.From, + To: transfer.Address, + FromAccount: from, + ToAccount: to, + Value: transfer.Value, + ERC20: string(transfer.Type) == "erc20", + Contract: transfer.Contract, + Network: transfer.NetworkID, + } + + return &Notification{ + ID: transfer.ID, + Body: body, + Deeplink: deeplink, + Category: "transaction", + } +} + +func (s *Service) transactionsHandler(payload TransactionEvent) { + log.Info("Handled a new transaction", "info", payload) + + limit := 20 + if payload.BlockNumber != nil { + for _, address := range payload.Accounts { + log.Info("Handled transfer for address", "info", address) + transfers, err := s.walletDB.GetTransfersByAddressAndBlock(address, payload.BlockNumber, int64(limit)) + if err != nil { + log.Error("Could not fetch transfers", "error", err) + } + + for _, transaction := range transfers { + n := s.buildTransactionNotification(transaction) + pushMessage(n) + } + } + } +} + +// SubscribeWallet - Subscribes to wallet signals +func (s *Service) SubscribeWallet(publisher *event.Feed) error { + s.walletTransmitter.publisher = publisher + + preference, err := s.db.GetWalletPreference() + + if err != nil { + log.Error("Failed to get wallet preference", "error", err) + return nil + } + + if preference.Enabled { + s.StartWalletWatcher() + } + + return nil +} + +// StartWalletWatcher - Forward wallet events to notifications +func (s *Service) StartWalletWatcher() { + if s.walletTransmitter.quit != nil { + // already running, nothing to do + return + } + + if s.walletTransmitter.publisher == nil { + log.Error("wallet publisher was not initialized") + return + } + + s.walletTransmitter.quit = make(chan struct{}) + events := make(chan wallet.Event, 10) + sub := s.walletTransmitter.publisher.Subscribe(events) + + s.walletTransmitter.wg.Add(1) + + go func() { + defer s.walletTransmitter.wg.Done() + for { + select { + case <-s.walletTransmitter.quit: + sub.Unsubscribe() + return + case err := <-sub.Err(): + // technically event.Feed cannot send an error to subscription.Err channel. + // the only time we will get an event is when that channel is closed. + if err != nil { + log.Error("wallet signals transmitter failed with", "error", err) + } + return + case event := <-events: + if event.Type == wallet.EventNewBlock { + s.transmitter.publisher.Send(TransactionEvent{ + Type: string(event.Type), + BlockNumber: event.BlockNumber, + Accounts: event.Accounts, + NewTransactionsPerAccount: event.NewTransactionsPerAccount, + ERC20: event.ERC20, + }) + } + } + } + }() +} + +// StopWalletWatcher - stops watching for new wallet events +func (s *Service) StopWalletWatcher() { + if s.walletTransmitter.quit != nil { + close(s.walletTransmitter.quit) + s.walletTransmitter.wg.Wait() + s.walletTransmitter.quit = nil + } +} + +// IsWatchingWallet - check if local-notifications are subscribed to wallet updates +func (s *Service) IsWatchingWallet() bool { + return s.walletTransmitter.quit != nil +} + +// Start Worker which processes all incoming messages +func (s *Service) Start(_ *p2p.Server) error { + s.started = true + + s.transmitter.quit = make(chan struct{}) + s.transmitter.publisher = &event.Feed{} + + events := make(chan TransactionEvent, 10) + sub := s.transmitter.publisher.Subscribe(events) + + s.transmitter.wg.Add(1) + go func() { + defer s.transmitter.wg.Done() + for { + select { + case <-s.transmitter.quit: + sub.Unsubscribe() + return + case err := <-sub.Err(): + if err != nil { + log.Error("Local notifications transmitter failed with", "error", err) + } + return + case event := <-events: + s.transactionsHandler(event) + } + } + }() + + log.Info("Successful start") + + return nil +} + +// Stop worker +func (s *Service) Stop() error { + s.started = false + + if s.transmitter.quit != nil { + close(s.transmitter.quit) + s.transmitter.wg.Wait() + s.transmitter.quit = nil + } + + if s.walletTransmitter.quit != nil { + close(s.walletTransmitter.quit) + s.walletTransmitter.wg.Wait() + s.walletTransmitter.quit = nil + } + + return nil +} + +// APIs returns list of available RPC APIs. +func (s *Service) APIs() []rpc.API { + return []rpc.API{ + { + Namespace: "localnotifications", + Version: "0.1.0", + Service: NewAPI(s), + Public: true, + }, + } +} + +// Protocols returns list of p2p protocols. +func (s *Service) Protocols() []p2p.Protocol { + return nil +} + +func (s *Service) IsStarted() bool { + return s.started +} diff --git a/services/local-notifications/core_test.go b/services/local-notifications/core_test.go new file mode 100644 index 000000000..3338f6cce --- /dev/null +++ b/services/local-notifications/core_test.go @@ -0,0 +1,135 @@ +package localnotifications + +import ( + "database/sql" + "encoding/json" + "fmt" + "math/big" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/status-im/status-go/services/wallet" + "github.com/status-im/status-go/signal" + "github.com/status-im/status-go/t/utils" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +func createWalletDb(t *testing.T, db *sql.DB) (*wallet.Database, func()) { + return wallet.NewDB(db, 1777), func() { + require.NoError(t, db.Close()) + } +} + +func TestServiceStartStop(t *testing.T) { + db, stop := setupAppTestDb(t) + defer stop() + + s := NewService(db, 1777) + require.NoError(t, s.Start(nil)) + require.Equal(t, true, s.IsStarted()) + + require.NoError(t, s.Stop()) + require.Equal(t, false, s.IsStarted()) +} + +func TestWalletSubscription(t *testing.T) { + db, stop := setupAppTestDb(t) + defer stop() + + feed := &event.Feed{} + s := NewService(db, 1777) + require.NoError(t, s.Start(nil)) + require.Equal(t, true, s.IsStarted()) + + require.NoError(t, s.SubscribeWallet(feed)) + require.Equal(t, false, s.IsWatchingWallet()) + + s.StartWalletWatcher() + require.Equal(t, true, s.IsWatchingWallet()) + + s.StopWalletWatcher() + require.Equal(t, false, s.IsWatchingWallet()) + + require.NoError(t, s.Stop()) + require.Equal(t, false, s.IsStarted()) +} + +func TestTransactionNotification(t *testing.T) { + db, stop := setupAppTestDb(t) + defer stop() + + walletDb, stop := createWalletDb(t, db) + defer stop() + + s := NewService(db, 1777) + require.NoError(t, s.Start(nil)) + require.Equal(t, true, s.IsStarted()) + + var signalEvent []byte + + signalHandler := signal.MobileSignalHandler(func(s []byte) { + signalEvent = s + }) + + signal.SetMobileSignalHandler(signalHandler) + + feed := &event.Feed{} + require.NoError(t, s.SubscribeWallet(feed)) + + s.StartWalletWatcher() + + header := &wallet.DBHeader{ + Number: big.NewInt(1), + Hash: common.Hash{1}, + Address: common.Address{1}, + } + tx := types.NewTransaction(1, common.Address{1}, nil, 10, big.NewInt(10), nil) + receipt := types.NewReceipt(nil, false, 100) + receipt.Logs = []*types.Log{} + transfers := []wallet.Transfer{ + { + ID: common.Hash{1}, + Type: wallet.TransferType("eth"), + BlockHash: header.Hash, + BlockNumber: header.Number, + Transaction: tx, + Receipt: receipt, + Address: header.Address, + }, + } + require.NoError(t, walletDb.ProcessBlocks(header.Address, big.NewInt(1), big.NewInt(1), []*wallet.DBHeader{header})) + require.NoError(t, walletDb.ProcessTranfers(transfers, []*wallet.DBHeader{})) + + feed.Send(wallet.Event{ + Type: wallet.EventNewBlock, + BlockNumber: header.Number, + Accounts: []common.Address{header.Address}, + }) + + require.NoError(t, utils.Eventually(func() error { + if signalEvent == nil { + return fmt.Errorf("Signal was not handled") + } + notification := struct { + Type string + Event Notification + }{} + + require.NoError(t, json.Unmarshal(signalEvent, ¬ification)) + + if notification.Type != "local-notifications" { + return fmt.Errorf("Wrong signal was sent") + } + if notification.Event.Body.To != header.Address { + return fmt.Errorf("Transaction to address is wrong") + } + return nil + }, 2*time.Second, 100*time.Millisecond)) + + require.NoError(t, s.Stop()) +} diff --git a/services/local-notifications/database.go b/services/local-notifications/database.go new file mode 100644 index 000000000..e30f08203 --- /dev/null +++ b/services/local-notifications/database.go @@ -0,0 +1,50 @@ +package localnotifications + +import ( + "database/sql" +) + +type Database struct { + db *sql.DB + network uint64 +} + +type NotificationPreference struct { + Enabled bool `json:"enabled"` + Service string `json:"service"` + Event string `json:"event,omitempty"` + Identifier string `json:"identifier,omitempty"` +} + +func NewDB(db *sql.DB, network uint64) *Database { + return &Database{db: db, network: network} +} + +func (db *Database) GetPreferences() (rst []NotificationPreference, err error) { + rows, err := db.db.Query("SELECT service, event, identifier, enabled FROM local_notifications_preferences") + if err != nil { + return nil, err + } + defer rows.Close() + for rows.Next() { + pref := NotificationPreference{} + err = rows.Scan(&pref.Service, &pref.Event, &pref.Identifier, &pref.Enabled) + if err != nil { + return nil, err + } + rst = append(rst, pref) + } + return rst, nil +} + +func (db *Database) GetWalletPreference() (rst NotificationPreference, err error) { + pref := db.db.QueryRow("SELECT service, event, identifier, enabled FROM local_notifications_preferences WHERE service = 'wallet' AND event = 'transaction' AND identifier = 'all'") + + err = pref.Scan(&rst.Service, &rst.Event, &rst.Identifier, &rst.Enabled) + return +} + +func (db *Database) ChangeWalletPreference(preference bool) error { + _, err := db.db.Exec("INSERT OR REPLACE INTO local_notifications_preferences (service, event, identifier, enabled) VALUES ('wallet', 'transaction', 'all', ?)", preference) + return err +} diff --git a/services/local-notifications/database_test.go b/services/local-notifications/database_test.go new file mode 100644 index 000000000..8bf1586a9 --- /dev/null +++ b/services/local-notifications/database_test.go @@ -0,0 +1,67 @@ +package localnotifications + +import ( + "database/sql" + "io/ioutil" + "os" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/status-im/status-go/appdatabase" +) + +func setupAppTestDb(t *testing.T) (*sql.DB, func()) { + tmpfile, err := ioutil.TempFile("", "local-notifications-tests-") + require.NoError(t, err) + db, err := appdatabase.InitializeDB(tmpfile.Name(), "local-notifications-tests") + require.NoError(t, err) + return db, func() { + require.NoError(t, os.Remove(tmpfile.Name())) + } +} + +func setupTestDB(t *testing.T, db *sql.DB) (*Database, func()) { + return NewDB(db, 1777), func() { + require.NoError(t, db.Close()) + } +} + +func TestWalletPreferences(t *testing.T) { + appDB, appStop := setupAppTestDb(t) + defer appStop() + + db, stop := setupTestDB(t, appDB) + defer stop() + + enabled := true + require.NoError(t, db.ChangeWalletPreference(enabled)) + rst, err := db.GetWalletPreference() + require.NoError(t, err) + require.Equal(t, enabled, rst.Enabled) + + enabled = false + require.NoError(t, db.ChangeWalletPreference(enabled)) + rst, err = db.GetWalletPreference() + require.Equal(t, enabled, rst.Enabled) + + require.NoError(t, err) +} + +func TestPreferences(t *testing.T) { + appDB, appStop := setupAppTestDb(t) + defer appStop() + + db, stop := setupTestDB(t, appDB) + defer stop() + + enabled := true + + require.NoError(t, db.ChangeWalletPreference(enabled)) + rst, err := db.GetPreferences() + + require.Equal(t, 1, len(rst)) + require.Equal(t, enabled, rst[0].Enabled) + + require.NoError(t, err) +} diff --git a/services/wallet/database.go b/services/wallet/database.go index 07bfcf1c1..7ba13bcb9 100644 --- a/services/wallet/database.go +++ b/services/wallet/database.go @@ -250,6 +250,23 @@ func (db *Database) GetTransfersByAddress(address common.Address, toBlock *big.I return query.Scan(rows) } +// GetTransfersByAddressAndBlock loads transfers for a given address and block. +func (db *Database) GetTransfersByAddressAndBlock(address common.Address, block *big.Int, limit int64) (rst []Transfer, err error) { + query := newTransfersQuery(). + FilterNetwork(db.network). + FilterAddress(address). + FilterBlockNumber(block). + FilterLoaded(1). + Limit(limit) + + rows, err := db.db.Query(query.String(), query.Args()...) + if err != nil { + return + } + defer rows.Close() + return query.Scan(rows) +} + // GetBlocksByAddress loads blocks for a given address. func (db *Database) GetBlocksByAddress(address common.Address, limit int) (rst []*big.Int, err error) { query := `SELECT blk_number FROM blocks diff --git a/services/wallet/database_test.go b/services/wallet/database_test.go index 30720458d..47c117526 100644 --- a/services/wallet/database_test.go +++ b/services/wallet/database_test.go @@ -122,11 +122,11 @@ func TestDBReorgTransfers(t *testing.T) { replacedTX := types.NewTransaction(2, common.Address{1}, nil, 10, big.NewInt(10), nil) require.NoError(t, db.ProcessBlocks(original.Address, original.Number, original.Number, []*DBHeader{original})) require.NoError(t, db.ProcessTranfers([]Transfer{ - {ethTransfer, common.Hash{1}, *originalTX.To(), original.Number, original.Hash, 100, originalTX, true, common.Address{1}, rcpt, nil}, + {ethTransfer, common.Hash{1}, *originalTX.To(), original.Number, original.Hash, 100, originalTX, true, 1777, common.Address{1}, rcpt, nil}, }, []*DBHeader{})) require.NoError(t, db.ProcessBlocks(replaced.Address, replaced.Number, replaced.Number, []*DBHeader{replaced})) require.NoError(t, db.ProcessTranfers([]Transfer{ - {ethTransfer, common.Hash{2}, *replacedTX.To(), replaced.Number, replaced.Hash, 100, replacedTX, true, common.Address{1}, rcpt, nil}, + {ethTransfer, common.Hash{2}, *replacedTX.To(), replaced.Number, replaced.Hash, 100, replacedTX, true, 1777, common.Address{1}, rcpt, nil}, }, []*DBHeader{original})) all, err := db.GetTransfers(big.NewInt(0), nil) diff --git a/services/wallet/downloader.go b/services/wallet/downloader.go index 4ac328ffd..cb67306ab 100644 --- a/services/wallet/downloader.go +++ b/services/wallet/downloader.go @@ -41,6 +41,7 @@ type Transfer struct { Timestamp uint64 `json:"timestamp"` Transaction *types.Transaction `json:"transaction"` Loaded bool + NetworkID uint64 // From is derived from tx signature in order to offload this computation from UI component. From common.Address `json:"from"` Receipt *types.Receipt `json:"receipt"` diff --git a/services/wallet/service.go b/services/wallet/service.go index 2c8e33104..ef43ec578 100644 --- a/services/wallet/service.go +++ b/services/wallet/service.go @@ -18,9 +18,11 @@ import ( func NewService(db *Database, accountsFeed *event.Feed) *Service { feed := &event.Feed{} return &Service{ - db: db, - feed: feed, - signals: &SignalsTransmitter{publisher: feed}, + db: db, + feed: feed, + signals: &SignalsTransmitter{ + publisher: feed, + }, accountsFeed: accountsFeed, } } @@ -44,6 +46,11 @@ func (s *Service) Start(*p2p.Server) error { return s.signals.Start() } +// GetFeed returns signals feed. +func (s *Service) GetFeed() *event.Feed { + return s.feed +} + // StartReactor separately because it requires known ethereum address, which will become available only after login. func (s *Service) StartReactor(client *ethclient.Client, accounts []common.Address, chain *big.Int) error { reactor := NewReactor(s.db, s.feed, client, chain) diff --git a/services/wallet/transfer_view.go b/services/wallet/transfer_view.go index 3d65cce10..6b479d016 100644 --- a/services/wallet/transfer_view.go +++ b/services/wallet/transfer_view.go @@ -32,6 +32,7 @@ func castToTransferView(t Transfer) TransferView { view.TxStatus = hexutil.Uint64(t.Receipt.Status) view.Input = hexutil.Bytes(t.Transaction.Data()) view.TxHash = t.Transaction.Hash() + view.NetworkID = t.NetworkID switch t.Type { case ethTransfer: view.From = t.From @@ -48,6 +49,11 @@ func castToTransferView(t Transfer) TransferView { return view } +// CastToTransferView transforms a raw Transfer into an enriched one +func CastToTransferView(t Transfer) TransferView { + return castToTransferView(t) +} + func parseLog(ethlog *types.Log) (from, to common.Address, amount *big.Int) { if len(ethlog.Topics) < 3 { log.Warn("not enough topics for erc20 transfer", "topics", ethlog.Topics) @@ -91,4 +97,5 @@ type TransferView struct { From common.Address `json:"from"` To common.Address `json:"to"` Contract common.Address `json:"contract"` + NetworkID uint64 } diff --git a/services/wallet/transfers_query.go b/services/wallet/transfers_query.go index 3cbf878fe..b05e087fe 100644 --- a/services/wallet/transfers_query.go +++ b/services/wallet/transfers_query.go @@ -9,7 +9,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" ) -const baseTransfersQuery = "SELECT hash, type, blk_hash, blk_number, timestamp, address, tx, sender, receipt, log FROM transfers" +const baseTransfersQuery = "SELECT hash, type, blk_hash, blk_number, timestamp, address, tx, sender, receipt, log, network_id FROM transfers" func newTransfersQuery() *transfersQuery { buf := bytes.NewBuffer(nil) @@ -84,6 +84,14 @@ func (q *transfersQuery) FilterBlockHash(blockHash common.Hash) *transfersQuery return q } +func (q *transfersQuery) FilterBlockNumber(blockNumber *big.Int) *transfersQuery { + q.andOrWhere() + q.added = true + q.buf.WriteString(" blk_number = ?") + q.args = append(q.args, (*SQLBigInt)(blockNumber)) + return q +} + func (q *transfersQuery) Limit(pageSize int64) *transfersQuery { q.buf.WriteString(" ORDER BY blk_number DESC, hash ASC ") q.buf.WriteString(" LIMIT ?") @@ -110,7 +118,7 @@ func (q *transfersQuery) Scan(rows *sql.Rows) (rst []Transfer, err error) { err = rows.Scan( &transfer.ID, &transfer.Type, &transfer.BlockHash, (*SQLBigInt)(transfer.BlockNumber), &transfer.Timestamp, &transfer.Address, - &JSONBlob{transfer.Transaction}, &transfer.From, &JSONBlob{transfer.Receipt}, &JSONBlob{transfer.Log}) + &JSONBlob{transfer.Transaction}, &transfer.From, &JSONBlob{transfer.Receipt}, &JSONBlob{transfer.Log}, &transfer.NetworkID) if err != nil { return nil, err } diff --git a/signal/events_pn.go b/signal/events_pn.go new file mode 100644 index 000000000..4f104b9b9 --- /dev/null +++ b/signal/events_pn.go @@ -0,0 +1,10 @@ +package signal + +const ( + notificationEvent = "local-notifications" +) + +// SendLocalNotifications sends event with a local notification. +func SendLocalNotifications(event interface{}) { + send(notificationEvent, event) +}