Local notifications service (#2026)

Also adds implementation for eth transactions notifications
This commit is contained in:
Gheorghe Pinzaru 2020-10-28 10:56:14 +03:00 committed by GitHub
parent 3e446eed8c
commit d04e54e54e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 969 additions and 74 deletions

View File

@ -1 +1 @@
0.62.15
0.62.16

View File

@ -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
}
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

View File

@ -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
}
@ -748,38 +790,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,
"0016_local_notifications_preferences.down.sql": _0016_local_notifications_preferencesDownSql,
"0016_local_notifications_preferences.up.sql": _0016_local_notifications_preferencesUpSql,
"doc.go": docGo,
}
// AssetDebug is true if the assets were built with the debug flag enabled.
const AssetDebug = false
// AssetDir returns the file names below a certain
// directory embedded in the file by go-bindata.
// For example if you run go-bindata on data/... and data contains the
@ -848,6 +918,8 @@ 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{}},
"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{}},
}}

View File

@ -0,0 +1 @@
DROP TABLE local_notifications_preferences;

View File

@ -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;

View File

@ -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) {

View File

@ -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

View File

@ -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()

View File

@ -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()

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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, &notification))
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())
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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

View File

@ -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)

View File

@ -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"`

View File

@ -20,7 +20,9 @@ func NewService(db *Database, accountsFeed *event.Feed) *Service {
return &Service{
db: db,
feed: feed,
signals: &SignalsTransmitter{publisher: 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)

View File

@ -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
}

View File

@ -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
}

10
signal/events_pn.go Normal file
View File

@ -0,0 +1,10 @@
package signal
const (
notificationEvent = "local-notifications"
)
// SendLocalNotifications sends event with a local notification.
func SendLocalNotifications(event interface{}) {
send(notificationEvent, event)
}