493 lines
12 KiB
Go
Raw Normal View History

2019-01-08 21:02:11 +01:00
package main
import (
"crypto/ecdsa"
"flag"
"fmt"
"log"
"math"
"os"
2019-04-30 10:24:33 +02:00
ossignal "os/signal"
2019-01-08 21:02:11 +01:00
"path/filepath"
"strings"
2019-04-30 10:24:33 +02:00
"syscall"
2019-01-08 21:02:11 +01:00
"github.com/ethereum/go-ethereum/crypto"
2019-04-12 16:53:26 +02:00
gethnode "github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/rpc"
2019-01-08 21:02:11 +01:00
"github.com/jroimartin/gocui"
"github.com/peterbourgon/ff"
"github.com/pkg/errors"
"github.com/status-im/status-console-client/protocol/adapters"
"github.com/status-im/status-console-client/protocol/client"
2019-04-12 16:53:26 +02:00
"github.com/status-im/status-console-client/protocol/gethservice"
"github.com/status-im/status-go/logutils"
2019-01-22 09:39:23 +01:00
"github.com/status-im/status-go/node"
2019-01-08 21:02:11 +01:00
"github.com/status-im/status-go/params"
2019-01-24 12:39:23 +01:00
"github.com/status-im/status-go/signal"
2019-01-08 21:02:11 +01:00
)
2019-01-24 12:39:23 +01:00
var g *gocui.Gui
2019-04-12 16:53:26 +02:00
var (
fs = flag.NewFlagSet("status-term-client", flag.ExitOnError)
logLevel = fs.String("log-level", "INFO", "log level")
2019-01-08 21:02:11 +01:00
2019-04-30 10:24:33 +02:00
keyHex = fs.String("keyhex", "", "pass a private key in hex")
noUI = fs.Bool("no-ui", false, "disable UI")
2019-04-12 16:53:26 +02:00
// flags acting like commands
createKeyPair = fs.Bool("create-key-pair", false, "creates and prints a key pair instead of running")
2019-01-08 21:02:11 +01:00
2019-04-12 16:53:26 +02:00
// flags for in-proc node
dataDir = fs.String("data-dir", filepath.Join(os.TempDir(), "status-term-client"), "data directory for Ethereum node")
fleet = fs.String("fleet", params.FleetBeta, fmt.Sprintf("Status nodes cluster to connect to: %s", []string{params.FleetBeta, params.FleetStaging}))
configFile = fs.String("node-config", "", "a JSON file with node config")
pfsEnabled = fs.Bool("pfs", false, "enable PFS")
2019-01-08 21:02:11 +01:00
2019-04-12 16:53:26 +02:00
// flags for external node
providerURI = fs.String("provider", "", "an URI pointing at a provider")
)
2019-01-22 09:39:23 +01:00
2019-04-12 16:53:26 +02:00
func main() {
if err := ff.Parse(fs, os.Args[1:]); err != nil {
exitErr(errors.Wrap(err, "failed to parse flags"))
}
err := os.MkdirAll(*dataDir, 0777)
if err != nil {
exitErr(err)
}
logPath := filepath.Join(*dataDir, "client.log")
logFile, err := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
exitErr(err)
}
log.SetOutput(logFile)
2019-01-08 21:02:11 +01:00
err = logutils.OverrideRootLog(true, *logLevel, logutils.FileOptions{Filename: filepath.Join(*dataDir, "status.log")}, false)
2019-04-12 16:53:26 +02:00
if err != nil {
log.Fatalf("failed to override root log: %v\n", err)
}
2019-01-08 21:02:11 +01:00
if *createKeyPair {
key, err := crypto.GenerateKey()
if err != nil {
exitErr(err)
}
fmt.Printf("Your private key: %#x\n", crypto.FromECDSA(key))
os.Exit(0)
}
var privateKey *ecdsa.PrivateKey
if *keyHex != "" {
k, err := crypto.HexToECDSA(strings.TrimPrefix(*keyHex, "0x"))
if err != nil {
exitErr(err)
}
privateKey = k
2019-01-24 12:39:23 +01:00
log.Printf("contact address: %#x", crypto.FromECDSAPub(&privateKey.PublicKey))
2019-01-08 21:02:11 +01:00
} else {
exitErr(errors.New("private key is required"))
}
2019-04-30 10:24:33 +02:00
// create database
address := crypto.PubkeyToAddress(privateKey.PublicKey)
dbPath := filepath.Join(*dataDir, "db.sql")
err = os.MkdirAll(*dataDir, 0700)
if err != nil {
exitErr(err)
}
db, err := client.InitializeDB(dbPath, address.String())
2019-04-30 10:24:33 +02:00
if err != nil {
exitErr(err)
}
defer db.Close()
2019-04-12 16:53:26 +02:00
// initialize protocol
2019-04-30 10:24:33 +02:00
var (
messenger *client.MessengerV2
2019-04-30 10:24:33 +02:00
)
2019-01-22 09:39:23 +01:00
if *providerURI != "" {
2019-04-30 10:24:33 +02:00
messenger, err = createMessengerWithURI(*providerURI, privateKey, db)
2019-01-22 09:39:23 +01:00
if err != nil {
2019-04-30 10:24:33 +02:00
exitErr(err)
2019-01-22 09:39:23 +01:00
}
2019-04-30 10:24:33 +02:00
} else {
messenger, err = createMessengerInProc(privateKey, db)
2019-01-22 09:39:23 +01:00
if err != nil {
2019-04-30 10:24:33 +02:00
exitErr(err)
2019-01-22 09:39:23 +01:00
}
2019-04-30 10:24:33 +02:00
}
2019-01-22 09:39:23 +01:00
2019-04-30 10:24:33 +02:00
adambContact, err := client.ContactWithPublicKey("adamb", "0x0493ac727e70ea62c4428caddf4da301ca67b699577988d6a782898acfd813addf79b2a2ca2c411499f2e0a12b7de4d00574cbddb442bec85789aea36b10f46895")
if err != nil {
exitErr(err)
}
if contacts, err := db.Contacts(); len(contacts) == 0 || err != nil {
debugContacts := []client.Contact{
2019-05-15 14:00:04 +03:00
{Name: "status", Type: client.ContactPublicRoom, Topic: "status"},
{Name: "status-core", Type: client.ContactPublicRoom, Topic: "status-core"},
{Name: "testing-adamb", Type: client.ContactPublicRoom, Topic: "testing-adamb"},
2019-04-30 10:24:33 +02:00
adambContact,
}
if err := db.SaveContacts(debugContacts); err != nil {
exitErr(err)
}
}
2019-05-15 14:00:04 +03:00
go func() {
err = messenger.Start()
if err != nil {
exitErr(err)
}
}()
2019-01-24 12:39:23 +01:00
2019-04-30 10:24:33 +02:00
if !*noUI {
if err := setupGUI(privateKey, messenger); err != nil {
exitErr(err)
}
2019-01-24 12:39:23 +01:00
2019-04-30 10:24:33 +02:00
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
exitErr(err)
2019-01-22 09:39:23 +01:00
}
2019-04-30 10:24:33 +02:00
g.Close()
} else {
sigs := make(chan os.Signal, 1)
done := make(chan bool, 1)
2019-01-22 09:39:23 +01:00
2019-04-30 10:24:33 +02:00
ossignal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
2019-04-12 16:53:26 +02:00
2019-04-30 10:24:33 +02:00
go func() {
sig := <-sigs
log.Printf("received signal: %v", sig)
done <- true
}()
2019-04-12 16:53:26 +02:00
2019-04-30 10:24:33 +02:00
<-done
}
}
2019-04-12 16:53:26 +02:00
2019-04-30 10:24:33 +02:00
func exitErr(err error) {
if g != nil {
g.Close()
}
2019-01-22 09:39:23 +01:00
2019-04-30 10:24:33 +02:00
fmt.Println(err)
os.Exit(1)
}
type keysGetter struct {
privateKey *ecdsa.PrivateKey
}
func (k keysGetter) PrivateKey() (*ecdsa.PrivateKey, error) {
return k.privateKey, nil
}
func createMessengerWithURI(uri string, pk *ecdsa.PrivateKey, db client.Database) (*client.MessengerV2, error) {
2019-04-30 10:24:33 +02:00
rpc, err := rpc.Dial(*providerURI)
if err != nil {
return nil, errors.Wrap(err, "failed to dial")
}
// TODO: provide Mail Servers in a different way.
nodeConfig, err := generateStatusNodeConfig(*dataDir, *fleet, *configFile)
if err != nil {
return nil, errors.Wrap(err, "failed to generate node config")
}
proto := adapters.NewWhisperClientAdapter(
rpc,
pk,
nodeConfig.ClusterConfig.TrustedMailServers,
)
messenger := client.NewMessengerV2(pk, proto, db)
return &messenger, nil
2019-04-30 10:24:33 +02:00
}
2019-01-22 09:39:23 +01:00
func createMessengerInProc(pk *ecdsa.PrivateKey, db client.Database) (*client.MessengerV2, error) {
2019-04-30 10:24:33 +02:00
// collect mail server request signals
signalsForwarder := newSignalForwarder()
go signalsForwarder.Start()
2019-04-12 16:53:26 +02:00
2019-04-30 10:24:33 +02:00
// setup signals handler
signal.SetDefaultNodeNotificationHandler(
filterMailTypesHandler(signalsForwarder.in),
2019-04-30 10:24:33 +02:00
)
2019-04-04 17:07:27 +02:00
2019-04-30 10:24:33 +02:00
nodeConfig, err := generateStatusNodeConfig(*dataDir, *fleet, *configFile)
if err != nil {
exitErr(errors.Wrap(err, "failed to generate node config"))
}
2019-04-04 17:07:27 +02:00
2019-04-30 10:24:33 +02:00
statusNode := node.New()
protocolGethService := gethservice.New(
statusNode,
&keysGetter{privateKey: pk},
)
2019-04-04 17:07:27 +02:00
2019-04-30 10:24:33 +02:00
services := []gethnode.ServiceConstructor{
func(ctx *gethnode.ServiceContext) (gethnode.Service, error) {
return protocolGethService, nil
},
}
if err := statusNode.Start(nodeConfig, services...); err != nil {
return nil, errors.Wrap(err, "failed to start node")
}
shhService, err := statusNode.WhisperService()
if err != nil {
return nil, errors.Wrap(err, "failed to get Whisper service")
}
adapter := adapters.NewWhisperServiceAdapter(statusNode, shhService, pk)
messenger := client.NewMessengerV2(pk, adapter, db)
2019-04-30 10:24:33 +02:00
protocolGethService.SetProtocol(adapter)
protocolGethService.SetMessenger(&messenger)
2019-04-30 10:24:33 +02:00
// TODO: should be removed from StatusNode
if *pfsEnabled {
databasesDir := filepath.Join(*dataDir, "databases")
if err := os.MkdirAll(databasesDir, 0755); err != nil {
exitErr(errors.Wrap(err, "failed to create databases dir"))
}
2019-04-04 17:07:27 +02:00
2019-04-30 10:24:33 +02:00
if err := adapter.InitPFS(databasesDir); err != nil {
exitErr(errors.Wrap(err, "initialize PFS"))
2019-04-04 17:07:27 +02:00
}
2019-04-30 10:24:33 +02:00
log.Printf("PFS has been initialized")
2019-01-22 09:39:23 +01:00
}
return &messenger, nil
2019-04-30 10:24:33 +02:00
}
func setupGUI(privateKey *ecdsa.PrivateKey, messenger *client.MessengerV2) error {
2019-04-30 10:24:33 +02:00
var err error
// global
2019-01-24 12:39:23 +01:00
g, err = gocui.NewGui(gocui.Output256)
2019-01-08 21:02:11 +01:00
if err != nil {
2019-04-30 10:24:33 +02:00
return err
2019-01-08 21:02:11 +01:00
}
// prepare views
vm := NewViewManager(nil, g)
2019-03-25 11:01:42 +01:00
notifications := NewNotificationViewController(&ViewController{vm, g, ViewNotification})
chat := NewChatViewController(
&ViewController{vm, g, ViewChat},
privateKey,
messenger,
2019-03-25 11:01:42 +01:00
func(err error) {
_ = notifications.Error("Chat error", fmt.Sprintf("%v", err))
},
)
2019-01-08 21:02:11 +01:00
contacts := NewContactsViewController(&ViewController{vm, g, ViewContacts}, messenger)
2019-04-19 13:22:18 +02:00
if err := contacts.LoadAndRefresh(); err != nil {
2019-04-30 10:24:33 +02:00
return err
}
2019-01-08 21:02:11 +01:00
2019-02-25 08:29:35 +01:00
inputMultiplexer := NewInputMultiplexer()
inputMultiplexer.AddHandler(DefaultMultiplexerPrefix, func(b []byte) error {
log.Printf("default multiplexer handler")
return chat.Send(b)
2019-02-25 08:29:35 +01:00
})
inputMultiplexer.AddHandler("/contact", ContactCmdFactory(contacts))
inputMultiplexer.AddHandler("/request", RequestCmdFactory(chat))
2019-01-08 21:02:11 +01:00
views := []*View{
&View{
Name: ViewContacts,
2019-03-25 11:01:42 +01:00
Enabled: true,
2019-01-08 21:02:11 +01:00
Cursor: true,
Highlight: true,
SelBgColor: gocui.ColorGreen,
SelFgColor: gocui.ColorBlack,
TopLeft: func(maxX, maxY int) (int, int) { return 0, 0 },
BottomRight: func(maxX, maxY int) (int, int) {
return int(math.Floor(float64(maxX) * 0.2)), maxY - 4
},
Keybindings: []Binding{
Binding{
Key: gocui.KeyArrowDown,
Mod: gocui.ModNone,
Handler: CursorDownHandler,
},
Binding{
Key: gocui.KeyArrowUp,
Mod: gocui.ModNone,
Handler: CursorUpHandler,
},
Binding{
Key: gocui.KeyEnter,
Mod: gocui.ModNone,
Handler: GetLineHandler(func(idx int, _ string) error {
contact, ok := contacts.ContactByIdx(idx)
if !ok {
return errors.New("contact could not be found")
}
// We need to call Select asynchronously,
// otherwise the main thread is blocked
// and nothing is rendered.
go func() {
if err := chat.Select(contact); err != nil {
log.Printf("[GetLineHandler] error selecting a chat: %v", err)
}
}()
return nil
2019-01-08 21:02:11 +01:00
}),
},
},
},
&View{
Name: ViewChat,
2019-03-25 11:01:42 +01:00
Enabled: true,
2019-01-08 21:02:11 +01:00
Cursor: true,
Autoscroll: false,
2019-01-08 21:02:11 +01:00
Highlight: true,
Wrap: true,
SelBgColor: gocui.ColorGreen,
SelFgColor: gocui.ColorBlack,
TopLeft: func(maxX, maxY int) (int, int) {
return int(math.Ceil(float64(maxX) * 0.2)), 0
},
BottomRight: func(maxX, maxY int) (int, int) {
return maxX - 1, maxY - 4
},
Keybindings: []Binding{
Binding{
Key: gocui.KeyArrowDown,
Mod: gocui.ModNone,
Handler: CursorDownHandler,
},
Binding{
Key: gocui.KeyArrowUp,
Mod: gocui.ModNone,
Handler: CursorUpHandler,
},
Binding{
Key: gocui.KeyHome,
Mod: gocui.ModNone,
Handler: func(g *gocui.Gui, v *gocui.View) error {
2019-04-30 10:24:33 +02:00
params, err := chat.RequestOptions(false)
if err != nil {
return err
}
2019-03-25 11:01:42 +01:00
if err := notifications.Debug("Messages request", fmt.Sprintf("%v", params)); err != nil {
return err
}
// RequestMessages needs to be called asynchronously,
// otherwise the main thread is blocked
// and nothing is rendered.
go func() {
if err := chat.RequestMessages(params); err != nil {
2019-03-25 11:01:42 +01:00
_ = notifications.Error("Request failed", fmt.Sprintf("%v", err))
}
}()
return HomeHandler(g, v)
},
},
Binding{
Key: gocui.KeyEnd,
Mod: gocui.ModNone,
Handler: EndHandler,
},
2019-01-08 21:02:11 +01:00
},
},
&View{
Name: ViewInput,
2019-03-25 11:01:42 +01:00
Enabled: true,
2019-01-08 21:02:11 +01:00
Editable: true,
Cursor: true,
Highlight: true,
TopLeft: func(maxX, maxY int) (int, int) {
return 0, maxY - 3
},
BottomRight: func(maxX, maxY int) (int, int) {
return maxX - 1, maxY - 1
},
Keybindings: []Binding{
Binding{
Key: gocui.KeyEnter,
Mod: gocui.ModNone,
2019-02-25 08:29:35 +01:00
Handler: inputMultiplexer.BindingHandler,
2019-01-08 21:02:11 +01:00
},
Binding{
Key: gocui.KeyEnter,
Mod: gocui.ModAlt,
Handler: MoveToNewLineHandler,
},
},
},
2019-03-25 11:01:42 +01:00
&View{
Name: ViewNotification,
Enabled: false,
Editable: false,
Cursor: false,
Highlight: true,
TopLeft: func(maxX, maxY int) (int, int) {
return maxX/2 - 50, maxY / 2
},
BottomRight: func(maxX, maxY int) (int, int) {
return maxX/2 + 50, maxY/2 + 2
},
Keybindings: []Binding{
Binding{
Key: gocui.KeyEnter,
Mod: gocui.ModNone,
Handler: func(g *gocui.Gui, v *gocui.View) error {
log.Printf("Notification Enter binding")
if err := vm.DisableView(ViewNotification); err != nil {
return err
}
if err := vm.DeleteView(ViewNotification); err != nil {
return err
}
return nil
},
},
},
},
2019-01-08 21:02:11 +01:00
}
bindings := []Binding{
Binding{
Key: gocui.KeyCtrlC,
Mod: gocui.ModNone,
Handler: QuitHandler,
},
Binding{
Key: gocui.KeyTab,
Mod: gocui.ModNone,
Handler: NextViewHandler(vm),
},
}
if err := vm.SetViews(views); err != nil {
2019-04-30 10:24:33 +02:00
return err
2019-01-08 21:02:11 +01:00
}
if err := vm.SetGlobalKeybindings(bindings); err != nil {
2019-04-30 10:24:33 +02:00
return err
2019-01-08 21:02:11 +01:00
}
2019-04-30 10:24:33 +02:00
return nil
2019-04-12 16:53:26 +02:00
}