parent
40b6b3da13
commit
9dbf5a0c86
|
@ -486,6 +486,20 @@ func (b *StatusBackend) Logout() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if b.statusNode.Config().BrowsersConfig.Enabled {
|
||||||
|
svc, err := b.statusNode.BrowsersService()
|
||||||
|
switch err {
|
||||||
|
case node.ErrServiceUnknown:
|
||||||
|
case nil:
|
||||||
|
err = svc.StopDatabase()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
b.AccountManager().Logout()
|
b.AccountManager().Logout()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -553,7 +567,11 @@ func (b *StatusBackend) SelectAccount(walletAddress, chatAddress, password strin
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return b.startWallet(password)
|
err = b.startWallet(password)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return b.startBrowsers(password)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *StatusBackend) startWallet(password string) error {
|
func (b *StatusBackend) startWallet(password string) error {
|
||||||
|
@ -575,6 +593,22 @@ func (b *StatusBackend) startWallet(password string) error {
|
||||||
new(big.Int).SetUint64(b.statusNode.Config().NetworkID))
|
new(big.Int).SetUint64(b.statusNode.Config().NetworkID))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *StatusBackend) startBrowsers(password string) error {
|
||||||
|
if !b.statusNode.Config().BrowsersConfig.Enabled {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
svc, err := b.statusNode.BrowsersService()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
account, err := b.accountManager.SelectedWalletAccount()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
path := path.Join(b.statusNode.Config().DataDir, fmt.Sprintf("browsers-%x.sql", account.Address))
|
||||||
|
return svc.StartDatabase(path, password)
|
||||||
|
}
|
||||||
|
|
||||||
// SendDataNotification sends data push notifications to users.
|
// SendDataNotification sends data push notifications to users.
|
||||||
// dataPayloadJSON is a JSON string that looks like this:
|
// dataPayloadJSON is a JSON string that looks like this:
|
||||||
// {
|
// {
|
||||||
|
|
18
node/node.go
18
node/node.go
|
@ -24,6 +24,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/p2p/nat"
|
"github.com/ethereum/go-ethereum/p2p/nat"
|
||||||
"github.com/status-im/status-go/mailserver"
|
"github.com/status-im/status-go/mailserver"
|
||||||
"github.com/status-im/status-go/params"
|
"github.com/status-im/status-go/params"
|
||||||
|
"github.com/status-im/status-go/services/browsers"
|
||||||
"github.com/status-im/status-go/services/incentivisation"
|
"github.com/status-im/status-go/services/incentivisation"
|
||||||
"github.com/status-im/status-go/services/peer"
|
"github.com/status-im/status-go/services/peer"
|
||||||
"github.com/status-im/status-go/services/personal"
|
"github.com/status-im/status-go/services/personal"
|
||||||
|
@ -47,6 +48,7 @@ var (
|
||||||
ErrPeerServiceRegistrationFailure = errors.New("failed to register the Peer service")
|
ErrPeerServiceRegistrationFailure = errors.New("failed to register the Peer service")
|
||||||
ErrIncentivisationServiceRegistrationFailure = errors.New("failed to register the Incentivisation service")
|
ErrIncentivisationServiceRegistrationFailure = errors.New("failed to register the Incentivisation service")
|
||||||
ErrWalletServiceRegistrationFailure = errors.New("failed to register the Wallet service")
|
ErrWalletServiceRegistrationFailure = errors.New("failed to register the Wallet service")
|
||||||
|
ErrBrowsersServiceRegistrationFailure = errors.New("failed to register the Browsers service")
|
||||||
)
|
)
|
||||||
|
|
||||||
// All general log messages in this package should be routed through this logger.
|
// All general log messages in this package should be routed through this logger.
|
||||||
|
@ -124,6 +126,10 @@ func MakeNode(config *params.NodeConfig, db *leveldb.DB) (*node.Node, error) {
|
||||||
return nil, fmt.Errorf("%v: %v", ErrWalletServiceRegistrationFailure, err)
|
return nil, fmt.Errorf("%v: %v", ErrWalletServiceRegistrationFailure, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := activateBrowsersService(stack, config.BrowsersConfig); err != nil {
|
||||||
|
return nil, fmt.Errorf("%v: %v", ErrBrowsersServiceRegistrationFailure, err)
|
||||||
|
}
|
||||||
|
|
||||||
return stack, nil
|
return stack, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,7 +283,7 @@ func activatePeerService(stack *node.Node) error {
|
||||||
|
|
||||||
func activateWalletService(stack *node.Node, config params.WalletConfig) error {
|
func activateWalletService(stack *node.Node, config params.WalletConfig) error {
|
||||||
if !config.Enabled {
|
if !config.Enabled {
|
||||||
logger.Info("service.Wallet is disabled")
|
logger.Info("wallet service is disabled")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return stack.Register(func(*node.ServiceContext) (node.Service, error) {
|
return stack.Register(func(*node.ServiceContext) (node.Service, error) {
|
||||||
|
@ -285,6 +291,16 @@ func activateWalletService(stack *node.Node, config params.WalletConfig) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func activateBrowsersService(stack *node.Node, config params.BrowsersConfig) error {
|
||||||
|
if !config.Enabled {
|
||||||
|
logger.Info("browsers service is disabled")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return stack.Register(func(*node.ServiceContext) (node.Service, error) {
|
||||||
|
return browsers.NewService(), nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func registerMailServer(whisperService *whisper.Whisper, config *params.WhisperConfig) (err error) {
|
func registerMailServer(whisperService *whisper.Whisper, config *params.WhisperConfig) (err error) {
|
||||||
var mailServer mailserver.WMailServer
|
var mailServer mailserver.WMailServer
|
||||||
whisperService.RegisterServer(&mailServer)
|
whisperService.RegisterServer(&mailServer)
|
||||||
|
|
|
@ -29,6 +29,7 @@ import (
|
||||||
"github.com/status-im/status-go/params"
|
"github.com/status-im/status-go/params"
|
||||||
"github.com/status-im/status-go/peers"
|
"github.com/status-im/status-go/peers"
|
||||||
"github.com/status-im/status-go/rpc"
|
"github.com/status-im/status-go/rpc"
|
||||||
|
"github.com/status-im/status-go/services/browsers"
|
||||||
"github.com/status-im/status-go/services/peer"
|
"github.com/status-im/status-go/services/peer"
|
||||||
"github.com/status-im/status-go/services/shhext"
|
"github.com/status-im/status-go/services/shhext"
|
||||||
"github.com/status-im/status-go/services/status"
|
"github.com/status-im/status-go/services/status"
|
||||||
|
@ -591,7 +592,7 @@ func (n *StatusNode) ShhExtService() (s *shhext.Service, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// WalletService returns wallet.Service instance if it is started.
|
// WalletService returns wallet.Service instance if it was started.
|
||||||
func (n *StatusNode) WalletService() (s *wallet.Service, err error) {
|
func (n *StatusNode) WalletService() (s *wallet.Service, err error) {
|
||||||
n.mu.RLock()
|
n.mu.RLock()
|
||||||
defer n.mu.RUnlock()
|
defer n.mu.RUnlock()
|
||||||
|
@ -602,6 +603,17 @@ func (n *StatusNode) WalletService() (s *wallet.Service, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BrowsersService returns browsers.Service instance if it was started.
|
||||||
|
func (n *StatusNode) BrowsersService() (s *browsers.Service, err error) {
|
||||||
|
n.mu.RLock()
|
||||||
|
defer n.mu.RUnlock()
|
||||||
|
err = n.gethService(&s)
|
||||||
|
if err == node.ErrServiceUnknown {
|
||||||
|
err = ErrServiceUnknown
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// AccountManager exposes reference to node's accounts manager
|
// AccountManager exposes reference to node's accounts manager
|
||||||
func (n *StatusNode) AccountManager() (*accounts.Manager, error) {
|
func (n *StatusNode) AccountManager() (*accounts.Manager, error) {
|
||||||
n.mu.RLock()
|
n.mu.RLock()
|
||||||
|
|
|
@ -346,6 +346,9 @@ type NodeConfig struct {
|
||||||
// WalletConfig extra configuration for wallet.Service.
|
// WalletConfig extra configuration for wallet.Service.
|
||||||
WalletConfig WalletConfig
|
WalletConfig WalletConfig
|
||||||
|
|
||||||
|
// BrowsersConfig extra configuration for browsers.Service.
|
||||||
|
BrowsersConfig BrowsersConfig
|
||||||
|
|
||||||
// SwarmConfig extra configuration for Swarm and ENS
|
// SwarmConfig extra configuration for Swarm and ENS
|
||||||
SwarmConfig SwarmConfig `json:"SwarmConfig," validate:"structonly"`
|
SwarmConfig SwarmConfig `json:"SwarmConfig," validate:"structonly"`
|
||||||
|
|
||||||
|
@ -366,6 +369,11 @@ type WalletConfig struct {
|
||||||
Enabled bool
|
Enabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BrowsersConfig extra configuration for browsers.Service.
|
||||||
|
type BrowsersConfig struct {
|
||||||
|
Enabled bool
|
||||||
|
}
|
||||||
|
|
||||||
// ShhextConfig defines options used by shhext service.
|
// ShhextConfig defines options used by shhext service.
|
||||||
type ShhextConfig struct {
|
type ShhextConfig struct {
|
||||||
PFSEnabled bool
|
PFSEnabled bool
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
Browsers Service
|
||||||
|
================
|
||||||
|
|
||||||
|
Browser service provides read/write API for browser object.
|
||||||
|
|
||||||
|
To enable include browsers config part and add `browsers` to APIModules:
|
||||||
|
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"BrowsersConfig": {
|
||||||
|
"Enabled": true,
|
||||||
|
},
|
||||||
|
APIModules: "browsers"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
API
|
||||||
|
---
|
||||||
|
|
||||||
|
Enabling service will expose three additional methods:
|
||||||
|
|
||||||
|
#### browsers_addBrowser
|
||||||
|
|
||||||
|
Stores browser in the database.
|
||||||
|
All fields are specified below:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"browser-id": "1",
|
||||||
|
"name": "first",
|
||||||
|
"timestamp": 10,
|
||||||
|
"dapp?": true,
|
||||||
|
"history-index": 1,
|
||||||
|
"history": [
|
||||||
|
"one",
|
||||||
|
"two"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### browsers_getBrowsers
|
||||||
|
|
||||||
|
Reads all browsers, returns in the format specified above. List is sorted by timestamp.
|
||||||
|
|
||||||
|
#### browsers_deleteBrowser
|
||||||
|
|
||||||
|
Delete browser from database. Accepts browser `id`.
|
|
@ -0,0 +1,41 @@
|
||||||
|
package browsers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrServiceNotInitialized returned when wallet is not initialized/started,.
|
||||||
|
ErrServiceNotInitialized = errors.New("browsers service is not initialized")
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewAPI(s *Service) *API {
|
||||||
|
return &API{s}
|
||||||
|
}
|
||||||
|
|
||||||
|
// API is class with methods available over RPC.
|
||||||
|
type API struct {
|
||||||
|
s *Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) AddBrowser(ctx context.Context, browser Browser) error {
|
||||||
|
if api.s.db == nil {
|
||||||
|
return ErrServiceNotInitialized
|
||||||
|
}
|
||||||
|
return api.s.db.InsertBrowser(browser)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) GetBrowsers(ctx context.Context) ([]*Browser, error) {
|
||||||
|
if api.s.db == nil {
|
||||||
|
return nil, ErrServiceNotInitialized
|
||||||
|
}
|
||||||
|
return api.s.db.GetBrowsers()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) DeleteBrowser(ctx context.Context, id string) error {
|
||||||
|
if api.s.db == nil {
|
||||||
|
return ErrServiceNotInitialized
|
||||||
|
}
|
||||||
|
return api.s.db.DeleteBrowser(id)
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
package browsers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setupTestDB(t *testing.T) (*Database, func()) {
|
||||||
|
tmpfile, err := ioutil.TempFile("", "browsers-tests-")
|
||||||
|
require.NoError(t, err)
|
||||||
|
db, err := InitializeDB(tmpfile.Name(), "browsers-tests")
|
||||||
|
require.NoError(t, err)
|
||||||
|
return db, func() {
|
||||||
|
require.NoError(t, db.Close())
|
||||||
|
require.NoError(t, os.Remove(tmpfile.Name()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupTestAPI(t *testing.T) (*API, func()) {
|
||||||
|
db, cancel := setupTestDB(t)
|
||||||
|
return &API{s: &Service{db: db}}, cancel
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBrowsersOrderedNewestFirst(t *testing.T) {
|
||||||
|
api, cancel := setupTestAPI(t)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
browsers := []*Browser{
|
||||||
|
{
|
||||||
|
ID: "1",
|
||||||
|
Name: "first",
|
||||||
|
Dapp: true,
|
||||||
|
Timestamp: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "2",
|
||||||
|
Name: "second",
|
||||||
|
Dapp: true,
|
||||||
|
Timestamp: 50,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "3",
|
||||||
|
Name: "third",
|
||||||
|
Dapp: true,
|
||||||
|
Timestamp: 100,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for i := 0; i < len(browsers); i++ {
|
||||||
|
require.NoError(t, api.AddBrowser(context.TODO(), *browsers[i]))
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(browsers, func(i, j int) bool {
|
||||||
|
return browsers[i].Timestamp > browsers[j].Timestamp
|
||||||
|
})
|
||||||
|
|
||||||
|
rst, err := api.GetBrowsers(context.TODO())
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, browsers, rst)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBrowsersHistoryIncluded(t *testing.T) {
|
||||||
|
api, cancel := setupTestAPI(t)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
browser := &Browser{
|
||||||
|
ID: "1",
|
||||||
|
Name: "first",
|
||||||
|
Dapp: true,
|
||||||
|
Timestamp: 10,
|
||||||
|
HistoryIndex: 1,
|
||||||
|
History: []string{"one", "two"},
|
||||||
|
}
|
||||||
|
require.NoError(t, api.AddBrowser(context.TODO(), *browser))
|
||||||
|
rst, err := api.GetBrowsers(context.TODO())
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, rst, 1)
|
||||||
|
require.Equal(t, browser, rst[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBrowsersReplaceOnUpdate(t *testing.T) {
|
||||||
|
api, cancel := setupTestAPI(t)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
browser := &Browser{
|
||||||
|
ID: "1",
|
||||||
|
Name: "first",
|
||||||
|
Dapp: true,
|
||||||
|
Timestamp: 10,
|
||||||
|
History: []string{"one", "two"},
|
||||||
|
}
|
||||||
|
require.NoError(t, api.AddBrowser(context.TODO(), *browser))
|
||||||
|
browser.Dapp = false
|
||||||
|
browser.History = []string{"one", "three"}
|
||||||
|
browser.Timestamp = 107
|
||||||
|
require.NoError(t, api.AddBrowser(context.TODO(), *browser))
|
||||||
|
rst, err := api.GetBrowsers(context.TODO())
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, rst, 1)
|
||||||
|
require.Equal(t, browser, rst[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeleteBrowser(t *testing.T) {
|
||||||
|
api, cancel := setupTestAPI(t)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
browser := &Browser{
|
||||||
|
ID: "1",
|
||||||
|
Name: "first",
|
||||||
|
Dapp: true,
|
||||||
|
Timestamp: 10,
|
||||||
|
History: []string{"one", "two"},
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, api.AddBrowser(context.TODO(), *browser))
|
||||||
|
rst, err := api.GetBrowsers(context.TODO())
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, rst, 1)
|
||||||
|
|
||||||
|
require.NoError(t, api.DeleteBrowser(context.TODO(), browser.ID))
|
||||||
|
rst, err = api.GetBrowsers(context.TODO())
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, rst, 0)
|
||||||
|
}
|
|
@ -0,0 +1,136 @@
|
||||||
|
package browsers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"github.com/status-im/status-go/services/browsers/migrations"
|
||||||
|
"github.com/status-im/status-go/sqlite"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Database sql wrapper for operations with browser objects.
|
||||||
|
type Database struct {
|
||||||
|
db *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes database.
|
||||||
|
func (db Database) Close() error {
|
||||||
|
return db.db.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitializeDB creates db file at a given path and applies migrations.
|
||||||
|
func InitializeDB(path, password string) (*Database, error) {
|
||||||
|
db, err := sqlite.OpenDB(path, password)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = migrations.Migrate(db)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Database{db: db}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Browser struct {
|
||||||
|
ID string `json:"browser-id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Timestamp uint64 `json:"timestamp"`
|
||||||
|
Dapp bool `json:"dapp?"`
|
||||||
|
HistoryIndex int `json:"history-index,omitempty"`
|
||||||
|
History []string `json:"history,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *Database) InsertBrowser(browser Browser) (err error) {
|
||||||
|
var (
|
||||||
|
tx *sql.Tx
|
||||||
|
insert *sql.Stmt
|
||||||
|
)
|
||||||
|
tx, err = db.db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err == nil {
|
||||||
|
err = tx.Commit()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = tx.Rollback()
|
||||||
|
}()
|
||||||
|
insert, err = tx.Prepare("INSERT OR REPLACE INTO browsers(id, name, timestamp, dapp, historyIndex) VALUES(?, ?, ?, ?, ?)")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = insert.Exec(browser.ID, browser.Name, browser.Timestamp, browser.Dapp, browser.HistoryIndex)
|
||||||
|
insert.Close()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(browser.History) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
insert, err = tx.Prepare("INSERT INTO browsers_history(browser_id, history) VALUES(?, ?)")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer insert.Close()
|
||||||
|
for _, history := range browser.History {
|
||||||
|
_, err = insert.Exec(browser.ID, history)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *Database) GetBrowsers() (rst []*Browser, err error) {
|
||||||
|
var (
|
||||||
|
tx *sql.Tx
|
||||||
|
rows *sql.Rows
|
||||||
|
)
|
||||||
|
tx, err = db.db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err == nil {
|
||||||
|
err = tx.Commit()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = tx.Rollback()
|
||||||
|
}()
|
||||||
|
// FULL and RIGHT joins are not supported
|
||||||
|
rows, err = tx.Query("SELECT id, name, timestamp, dapp, historyIndex FROM browsers ORDER BY timestamp DESC")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
browsers := map[string]*Browser{}
|
||||||
|
for rows.Next() {
|
||||||
|
browser := Browser{}
|
||||||
|
err = rows.Scan(&browser.ID, &browser.Name, &browser.Timestamp, &browser.Dapp, &browser.HistoryIndex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
browsers[browser.ID] = &browser
|
||||||
|
rst = append(rst, &browser)
|
||||||
|
}
|
||||||
|
rows, err = tx.Query("SELECT browser_id, history from browsers_history")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
id string
|
||||||
|
history string
|
||||||
|
)
|
||||||
|
for rows.Next() {
|
||||||
|
err = rows.Scan(&id, &history)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
browsers[id].History = append(browsers[id].History, history)
|
||||||
|
}
|
||||||
|
return rst, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *Database) DeleteBrowser(id string) error {
|
||||||
|
_, err := db.db.Exec("DELETE from browsers WHERE id = ?", id)
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,127 @@
|
||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func bindata_read(data []byte, name string) ([]byte, error) {
|
||||||
|
gz, err := gzip.NewReader(bytes.NewBuffer(data))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Read %q: %v", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
_, err = io.Copy(&buf, gz)
|
||||||
|
gz.Close()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Read %q: %v", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var __0001_browsers_down_sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\x48\x2a\xca\x2f\x2f\x4e\x2d\x2a\xb6\xe6\xc2\x22\x18\x9f\x91\x59\x5c\x92\x5f\x54\x69\xcd\x05\x08\x00\x00\xff\xff\x5b\xe2\x78\xbe\x32\x00\x00\x00")
|
||||||
|
|
||||||
|
func _0001_browsers_down_sql() ([]byte, error) {
|
||||||
|
return bindata_read(
|
||||||
|
__0001_browsers_down_sql,
|
||||||
|
"0001_browsers.down.sql",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
var __0001_browsers_up_sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8c\x8f\xc1\x6a\x83\x40\x10\x86\xef\xf3\x14\xff\x51\xc1\x37\xe8\x69\xd5\xd1\x0e\xdd\xee\x96\x75\x24\xc9\x29\x58\xb4\x54\xa8\x89\xa8\xd0\xf6\xed\xcb\x92\x04\xaf\xbd\x7e\xcc\x37\xfc\x5f\x11\xd8\x28\x43\x4d\x6e\x19\x52\xc1\x79\x05\x1f\xa5\xd1\x06\xef\xcb\xf5\x7b\x1d\x96\x15\x09\x8d\x3d\x94\x8f\x8a\xb7\x20\xaf\x26\x9c\xf0\xc2\xa7\x8c\x2e\xdd\x34\xdc\x70\x94\x5c\x6b\x6d\x46\xdb\x38\x0d\xeb\xd6\x4d\x33\xda\xa6\x96\xda\x71\x89\x5c\x6a\x71\x9a\x51\xdf\xcd\x33\x72\xef\x2d\x1b\x87\x92\x2b\xd3\x5a\xc5\x47\xf7\xb5\x0e\x19\x7d\x8e\xeb\x76\x5d\x7e\xe5\xd2\x0f\x3f\x68\x5d\x73\x33\xc5\x29\xa5\x38\x88\x3e\xfb\x56\x11\xfc\x41\xca\x27\xa2\x7f\x2c\x3e\xdf\xff\x21\xa1\x3b\x3a\x3f\x0a\xf6\xa9\x8f\x9b\x88\x33\xaa\x7c\x60\xa9\x5d\x2c\x4b\x76\x27\x45\xe0\x8a\x03\xbb\x82\xf7\xef\x49\xe4\x3e\x36\x58\x56\x46\x61\x9a\xc2\x94\x4c\x29\xfd\x05\x00\x00\xff\xff\x69\xa0\xeb\xdb\x4c\x01\x00\x00")
|
||||||
|
|
||||||
|
func _0001_browsers_up_sql() ([]byte, error) {
|
||||||
|
return bindata_read(
|
||||||
|
__0001_browsers_up_sql,
|
||||||
|
"0001_browsers.up.sql",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _doc_go = []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 doc_go() ([]byte, error) {
|
||||||
|
return bindata_read(
|
||||||
|
_doc_go,
|
||||||
|
"doc.go",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Asset loads and returns the asset for the given name.
|
||||||
|
// It returns an error if the asset could not be found or
|
||||||
|
// could not be loaded.
|
||||||
|
func Asset(name string) ([]byte, error) {
|
||||||
|
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||||
|
if f, ok := _bindata[cannonicalName]; ok {
|
||||||
|
return f()
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("Asset %s not found", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssetNames returns the names of the assets.
|
||||||
|
func AssetNames() []string {
|
||||||
|
names := make([]string, 0, len(_bindata))
|
||||||
|
for name := range _bindata {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
|
||||||
|
// _bindata is a table, holding each asset generator, mapped to its name.
|
||||||
|
var _bindata = map[string]func() ([]byte, error){
|
||||||
|
"0001_browsers.down.sql": _0001_browsers_down_sql,
|
||||||
|
"0001_browsers.up.sql": _0001_browsers_up_sql,
|
||||||
|
"doc.go": doc_go,
|
||||||
|
}
|
||||||
|
// 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
|
||||||
|
// following hierarchy:
|
||||||
|
// data/
|
||||||
|
// foo.txt
|
||||||
|
// img/
|
||||||
|
// a.png
|
||||||
|
// b.png
|
||||||
|
// then AssetDir("data") would return []string{"foo.txt", "img"}
|
||||||
|
// AssetDir("data/img") would return []string{"a.png", "b.png"}
|
||||||
|
// AssetDir("foo.txt") and AssetDir("notexist") would return an error
|
||||||
|
// AssetDir("") will return []string{"data"}.
|
||||||
|
func AssetDir(name string) ([]string, error) {
|
||||||
|
node := _bintree
|
||||||
|
if len(name) != 0 {
|
||||||
|
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||||
|
pathList := strings.Split(cannonicalName, "/")
|
||||||
|
for _, p := range pathList {
|
||||||
|
node = node.Children[p]
|
||||||
|
if node == nil {
|
||||||
|
return nil, fmt.Errorf("Asset %s not found", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if node.Func != nil {
|
||||||
|
return nil, fmt.Errorf("Asset %s not found", name)
|
||||||
|
}
|
||||||
|
rv := make([]string, 0, len(node.Children))
|
||||||
|
for name := range node.Children {
|
||||||
|
rv = append(rv, name)
|
||||||
|
}
|
||||||
|
return rv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type _bintree_t struct {
|
||||||
|
Func func() ([]byte, error)
|
||||||
|
Children map[string]*_bintree_t
|
||||||
|
}
|
||||||
|
var _bintree = &_bintree_t{nil, map[string]*_bintree_t{
|
||||||
|
"0001_browsers.down.sql": &_bintree_t{_0001_browsers_down_sql, map[string]*_bintree_t{
|
||||||
|
}},
|
||||||
|
"0001_browsers.up.sql": &_bintree_t{_0001_browsers_up_sql, map[string]*_bintree_t{
|
||||||
|
}},
|
||||||
|
"doc.go": &_bintree_t{doc_go, map[string]*_bintree_t{
|
||||||
|
}},
|
||||||
|
}}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
bindata "github.com/status-im/migrate/v4/source/go_bindata"
|
||||||
|
"github.com/status-im/status-go/sqlite"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Migrate applies migrations.
|
||||||
|
func Migrate(db *sql.DB) error {
|
||||||
|
return sqlite.Migrate(db, bindata.Resource(
|
||||||
|
AssetNames(),
|
||||||
|
func(name string) ([]byte, error) {
|
||||||
|
return Asset(name)
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
DROP TABLE browsers;
|
||||||
|
DROP TABLE browsers_history;
|
|
@ -0,0 +1,13 @@
|
||||||
|
CREATE TABLE IF NOT EXISTS browsers (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
timestamp USGIGNED BIGINT,
|
||||||
|
dapp BOOLEAN DEFAULT false,
|
||||||
|
historyIndex UNSIGNED INT
|
||||||
|
) WITHOUT ROWID;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS browsers_history (
|
||||||
|
browser_id TEXT NOT NULL,
|
||||||
|
history TEXT,
|
||||||
|
FOREIGN KEY(browser_id) REFERENCES browsers(id) ON DELETE CASCADE
|
||||||
|
)
|
|
@ -0,0 +1,3 @@
|
||||||
|
package sql
|
||||||
|
|
||||||
|
//go:generate go-bindata -pkg migrations -o ../bindata.go ./
|
|
@ -0,0 +1,63 @@
|
||||||
|
package browsers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewService initializes service instance.
|
||||||
|
func NewService() *Service {
|
||||||
|
return &Service{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Service is a browsers service.
|
||||||
|
type Service struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
db *Database
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start a service.
|
||||||
|
func (s *Service) Start(*p2p.Server) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start database after dbpath and password will become known.
|
||||||
|
func (s *Service) StartDatabase(dbpath, password string) (err error) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
s.db, err = InitializeDB(dbpath, password)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) StopDatabase() error {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
if s.db != nil {
|
||||||
|
return s.db.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop a service.
|
||||||
|
func (s *Service) Stop() error {
|
||||||
|
return s.StopDatabase()
|
||||||
|
}
|
||||||
|
|
||||||
|
// APIs returns list of available RPC APIs.
|
||||||
|
func (s *Service) APIs() []rpc.API {
|
||||||
|
return []rpc.API{
|
||||||
|
{
|
||||||
|
Namespace: "browsers",
|
||||||
|
Version: "0.1.0",
|
||||||
|
Service: NewAPI(s),
|
||||||
|
Public: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Protocols returns list of p2p protocols.
|
||||||
|
func (s *Service) Protocols() []p2p.Protocol {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -3,41 +3,16 @@ package migrations
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
|
||||||
"github.com/status-im/migrate/v4"
|
|
||||||
"github.com/status-im/migrate/v4/database/sqlcipher"
|
|
||||||
bindata "github.com/status-im/migrate/v4/source/go_bindata"
|
bindata "github.com/status-im/migrate/v4/source/go_bindata"
|
||||||
|
"github.com/status-im/status-go/sqlite"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Migrate applies migrations.
|
// Migrate applies migrations.
|
||||||
func Migrate(db *sql.DB) error {
|
func Migrate(db *sql.DB) error {
|
||||||
resources := bindata.Resource(
|
return sqlite.Migrate(db, bindata.Resource(
|
||||||
AssetNames(),
|
AssetNames(),
|
||||||
func(name string) ([]byte, error) {
|
func(name string) ([]byte, error) {
|
||||||
return Asset(name)
|
return Asset(name)
|
||||||
},
|
},
|
||||||
)
|
))
|
||||||
|
|
||||||
source, err := bindata.WithInstance(resources)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
driver, err := sqlcipher.WithInstance(db, &sqlcipher.Config{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
m, err := migrate.NewWithInstance(
|
|
||||||
"go-bindata",
|
|
||||||
source,
|
|
||||||
"sqlcipher",
|
|
||||||
driver)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = m.Up(); err != migrate.ErrNoChange {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
package sqlite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"github.com/status-im/migrate/v4"
|
||||||
|
"github.com/status-im/migrate/v4/database/sqlcipher"
|
||||||
|
bindata "github.com/status-im/migrate/v4/source/go_bindata"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Migrate database using provided resources.
|
||||||
|
func Migrate(db *sql.DB, resources *bindata.AssetSource) error {
|
||||||
|
source, err := bindata.WithInstance(resources)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
driver, err := sqlcipher.WithInstance(db, &sqlcipher.Config{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
m, err := migrate.NewWithInstance(
|
||||||
|
"go-bindata",
|
||||||
|
source,
|
||||||
|
"sqlcipher",
|
||||||
|
driver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = m.Up(); err != migrate.ErrNoChange {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue