mirror of
https://github.com/status-im/status-console-client.git
synced 2025-02-24 16:48:09 +00:00
align WhisperClient with WhisperService (#17)
This commit is contained in:
parent
3ada0cd593
commit
b80668730a
61
protocol/adapters/peer_utils.go
Normal file
61
protocol/adapters/peer_utils.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package adapters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrNoRunningNode node is not running.
|
||||||
|
ErrNoRunningNode = errors.New("there is no running node")
|
||||||
|
// ErrEmptyPeerURL provided peer URL is empty
|
||||||
|
ErrEmptyPeerURL = errors.New("empty peer url")
|
||||||
|
)
|
||||||
|
|
||||||
|
// waitForPeer waits for a peer to be added
|
||||||
|
func waitForPeer(p *p2p.Server, u string, e p2p.PeerEventType, t time.Duration, subscribed chan struct{}) error {
|
||||||
|
if p == nil {
|
||||||
|
return ErrNoRunningNode
|
||||||
|
}
|
||||||
|
if u == "" {
|
||||||
|
return ErrEmptyPeerURL
|
||||||
|
}
|
||||||
|
parsedPeer, err := enode.ParseV4(u)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ch := make(chan *p2p.PeerEvent)
|
||||||
|
subscription := p.SubscribeEvents(ch)
|
||||||
|
defer subscription.Unsubscribe()
|
||||||
|
close(subscribed)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case ev := <-ch:
|
||||||
|
if ev.Type == e && ev.Peer == parsedPeer.ID() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
case err := <-subscription.Err():
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case <-time.After(t):
|
||||||
|
return errors.New("wait for peer: timeout")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// waitForPeerAsync waits for a peer to be added asynchronously
|
||||||
|
func waitForPeerAsync(p *p2p.Server, u string, e p2p.PeerEventType, t time.Duration) <-chan error {
|
||||||
|
subscribed := make(chan struct{})
|
||||||
|
errCh := make(chan error)
|
||||||
|
go func() {
|
||||||
|
errCh <- waitForPeer(p, u, e, t, subscribed)
|
||||||
|
}()
|
||||||
|
<-subscribed
|
||||||
|
return errCh
|
||||||
|
}
|
@ -8,11 +8,13 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/status-im/status-console-client/protocol/v1"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
"github.com/status-im/status-console-client/protocol/v1"
|
|
||||||
"github.com/status-im/status-go/services/shhext"
|
|
||||||
"github.com/status-im/whisper/shhclient"
|
"github.com/status-im/whisper/shhclient"
|
||||||
|
|
||||||
whisper "github.com/status-im/whisper/whisperv6"
|
whisper "github.com/status-im/whisper/whisperv6"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -23,6 +25,7 @@ type WhisperClientAdapter struct {
|
|||||||
rpcClient *rpc.Client
|
rpcClient *rpc.Client
|
||||||
shhClient *shhclient.Client
|
shhClient *shhclient.Client
|
||||||
mailServerEnodes []string
|
mailServerEnodes []string
|
||||||
|
selectedMailServerEnode string
|
||||||
|
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
passSymKeyCache map[string]string
|
passSymKeyCache map[string]string
|
||||||
@ -48,24 +51,16 @@ func (a *WhisperClientAdapter) Subscribe(
|
|||||||
in chan<- *protocol.Message,
|
in chan<- *protocol.Message,
|
||||||
options protocol.SubscribeOptions,
|
options protocol.SubscribeOptions,
|
||||||
) (*protocol.Subscription, error) {
|
) (*protocol.Subscription, error) {
|
||||||
|
if err := options.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
criteria := whisper.Criteria{
|
criteria := whisper.Criteria{
|
||||||
MinPow: 0, // TODO: set it to proper value
|
MinPow: 0, // TODO: set it to proper value
|
||||||
AllowP2P: true, // messages from mail server are direct p2p messages
|
AllowP2P: true, // messages from mail server are direct p2p messages
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.IsPublic() {
|
if options.Identity != nil {
|
||||||
symKeyID, err := a.getOrAddSymKey(ctx, options.ChatName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
criteria.SymKeyID = symKeyID
|
|
||||||
|
|
||||||
topic, err := PublicChatTopic(options.ChatName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
criteria.Topics = append(criteria.Topics, topic)
|
|
||||||
} else {
|
|
||||||
identityID, err := a.shhClient.AddPrivateKey(ctx, crypto.FromECDSA(options.Identity))
|
identityID, err := a.shhClient.AddPrivateKey(ctx, crypto.FromECDSA(options.Identity))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -79,6 +74,20 @@ func (a *WhisperClientAdapter) Subscribe(
|
|||||||
criteria.Topics = append(criteria.Topics, topic)
|
criteria.Topics = append(criteria.Topics, topic)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if options.ChatName != "" {
|
||||||
|
symKeyID, err := a.getOrAddSymKey(ctx, options.ChatName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
criteria.SymKeyID = symKeyID
|
||||||
|
|
||||||
|
topic, err := PublicChatTopic(options.ChatName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
criteria.Topics = append(criteria.Topics, topic)
|
||||||
|
}
|
||||||
|
|
||||||
return a.subscribeMessages(ctx, criteria, in)
|
return a.subscribeMessages(ctx, criteria, in)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,58 +146,72 @@ func (a *WhisperClientAdapter) Send(
|
|||||||
data []byte,
|
data []byte,
|
||||||
options protocol.SendOptions,
|
options protocol.SendOptions,
|
||||||
) ([]byte, error) {
|
) ([]byte, error) {
|
||||||
|
if err := options.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
identityID, err := a.shhClient.AddPrivateKey(ctx, crypto.FromECDSA(options.Identity))
|
identityID, err := a.shhClient.AddPrivateKey(ctx, crypto.FromECDSA(options.Identity))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
newMessage := whisper.NewMessage{
|
message, err := a.createNewMessage(data, identityID, options)
|
||||||
TTL: 60,
|
|
||||||
Payload: data,
|
|
||||||
PowTarget: 2.0,
|
|
||||||
PowTime: 5,
|
|
||||||
Sig: identityID,
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.IsPublic() {
|
|
||||||
symKeyID, err := a.getOrAddSymKey(ctx, options.ChatName)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
newMessage.SymKeyID = symKeyID
|
|
||||||
|
|
||||||
topic, err := PublicChatTopic(options.ChatName)
|
hash, err := a.shhClient.Post(ctx, message)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
newMessage.Topic = topic
|
|
||||||
} else {
|
|
||||||
newMessage.PublicKey = crypto.FromECDSAPub(options.Recipient)
|
|
||||||
|
|
||||||
topic, err := PrivateChatTopic()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
newMessage.Topic = topic
|
|
||||||
}
|
|
||||||
|
|
||||||
hash, err := a.shhClient.Post(ctx, newMessage)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return hex.DecodeString(hash)
|
return hex.DecodeString(hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *WhisperClientAdapter) createNewMessage(data []byte, sigKey string, options protocol.SendOptions) (whisper.NewMessage, error) {
|
||||||
|
message := createWhisperNewMessage(data, sigKey)
|
||||||
|
|
||||||
|
if options.Recipient != nil {
|
||||||
|
message.PublicKey = crypto.FromECDSAPub(options.Recipient)
|
||||||
|
|
||||||
|
topic, err := PrivateChatTopic()
|
||||||
|
if err != nil {
|
||||||
|
return message, err
|
||||||
|
}
|
||||||
|
message.Topic = topic
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.ChatName != "" {
|
||||||
|
ctx := context.Background()
|
||||||
|
symKeyID, err := a.getOrAddSymKey(ctx, options.ChatName)
|
||||||
|
if err != nil {
|
||||||
|
return message, err
|
||||||
|
}
|
||||||
|
message.SymKeyID = symKeyID
|
||||||
|
|
||||||
|
topic, err := PublicChatTopic(options.ChatName)
|
||||||
|
if err != nil {
|
||||||
|
return message, err
|
||||||
|
}
|
||||||
|
message.Topic = topic
|
||||||
|
}
|
||||||
|
|
||||||
|
return message, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Request sends a request to MailServer for historic messages.
|
// Request sends a request to MailServer for historic messages.
|
||||||
func (a *WhisperClientAdapter) Request(ctx context.Context, params protocol.RequestOptions) error {
|
func (a *WhisperClientAdapter) Request(ctx context.Context, params protocol.RequestOptions) error {
|
||||||
enode, err := a.addMailServer(ctx)
|
enode, err := a.selectAndAddMailServer(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return a.requestMessages(ctx, enode, params)
|
return a.requestMessages(ctx, enode, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *WhisperClientAdapter) addMailServer(ctx context.Context) (string, error) {
|
func (a *WhisperClientAdapter) selectAndAddMailServer(ctx context.Context) (string, error) {
|
||||||
|
if a.selectedMailServerEnode != "" {
|
||||||
|
return a.selectedMailServerEnode, nil
|
||||||
|
}
|
||||||
|
|
||||||
enode := randomItem(a.mailServerEnodes)
|
enode := randomItem(a.mailServerEnodes)
|
||||||
|
|
||||||
if err := a.rpcClient.CallContext(ctx, nil, "admin_addPeer", enode); err != nil {
|
if err := a.rpcClient.CallContext(ctx, nil, "admin_addPeer", enode); err != nil {
|
||||||
@ -198,8 +221,6 @@ func (a *WhisperClientAdapter) addMailServer(ctx context.Context) (string, error
|
|||||||
// Adding peer is asynchronous operation so we need to retry a few times.
|
// Adding peer is asynchronous operation so we need to retry a few times.
|
||||||
retries := 0
|
retries := 0
|
||||||
for {
|
for {
|
||||||
<-time.After(time.Second)
|
|
||||||
|
|
||||||
err := a.shhClient.MarkTrustedPeer(ctx, enode)
|
err := a.shhClient.MarkTrustedPeer(ctx, enode)
|
||||||
if ctx.Err() == context.Canceled {
|
if ctx.Err() == context.Canceled {
|
||||||
log.Printf("requesting public messages canceled")
|
log.Printf("requesting public messages canceled")
|
||||||
@ -210,50 +231,55 @@ func (a *WhisperClientAdapter) addMailServer(ctx context.Context) (string, error
|
|||||||
}
|
}
|
||||||
if retries < 3 {
|
if retries < 3 {
|
||||||
retries++
|
retries++
|
||||||
|
<-time.After(time.Second)
|
||||||
} else {
|
} else {
|
||||||
return "", fmt.Errorf("failed to mark peer as trusted: %v", err)
|
return "", fmt.Errorf("failed to mark peer as trusted: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.selectedMailServerEnode = enode
|
||||||
|
|
||||||
return enode, nil
|
return enode, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *WhisperClientAdapter) requestMessages(ctx context.Context, enode string, params protocol.RequestOptions) error {
|
func (a *WhisperClientAdapter) requestMessages(ctx context.Context, enode string, params protocol.RequestOptions) error {
|
||||||
log.Printf("requesting messages from node %s", enode)
|
log.Printf("requesting messages from node %s", enode)
|
||||||
|
|
||||||
req, err := a.createMessagesRequest(enode, params)
|
arg, err := a.createMessagesRequest(enode, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return a.rpcClient.CallContext(ctx, nil, "shhext_requestMessages", req)
|
return a.rpcClient.CallContext(ctx, nil, "shhext_requestMessages", arg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *WhisperClientAdapter) createMessagesRequest(
|
func (a *WhisperClientAdapter) createMessagesRequest(
|
||||||
enode string,
|
enode string,
|
||||||
params protocol.RequestOptions,
|
params protocol.RequestOptions,
|
||||||
) (req shhext.MessagesRequest, err error) {
|
) (req shhextRequestMessagesParam, err error) {
|
||||||
mailSymKeyID, err := a.getOrAddSymKey(context.Background(), MailServerPassword)
|
mailSymKeyID, err := a.getOrAddSymKey(context.Background(), MailServerPassword)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return req, err
|
return req, err
|
||||||
}
|
}
|
||||||
|
|
||||||
req = shhext.MessagesRequest{
|
req = shhextRequestMessagesParam{
|
||||||
MailServerPeer: enode,
|
MailServerPeer: enode,
|
||||||
From: uint32(params.From), // TODO: change to int in status-go
|
From: params.From,
|
||||||
To: uint32(params.To), // TODO: change to int in status-go
|
To: params.To,
|
||||||
Limit: uint32(params.Limit), // TODO: change to int in status-go
|
Limit: params.Limit,
|
||||||
SymKeyID: mailSymKeyID,
|
SymKeyID: mailSymKeyID,
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.IsPublic() {
|
if params.Recipient != nil {
|
||||||
topic, err := PublicChatTopic(params.ChatName)
|
topic, err := PrivateChatTopic()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return req, err
|
return req, err
|
||||||
}
|
}
|
||||||
req.Topics = append(req.Topics, topic)
|
req.Topics = append(req.Topics, topic)
|
||||||
} else {
|
}
|
||||||
topic, err := PrivateChatTopic()
|
|
||||||
|
if params.ChatName != "" {
|
||||||
|
topic, err := PublicChatTopic(params.ChatName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return req, err
|
return req, err
|
||||||
}
|
}
|
||||||
|
@ -8,12 +8,13 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
|
||||||
"github.com/status-im/status-console-client/protocol/v1"
|
|
||||||
"github.com/status-im/status-go/node"
|
"github.com/status-im/status-go/node"
|
||||||
"github.com/status-im/status-go/services/shhext"
|
"github.com/status-im/status-go/services/shhext"
|
||||||
"github.com/status-im/status-go/t/helpers"
|
|
||||||
|
"github.com/status-im/status-console-client/protocol/v1"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
|
|
||||||
whisper "github.com/status-im/whisper/whisperv6"
|
whisper "github.com/status-im/whisper/whisperv6"
|
||||||
)
|
)
|
||||||
@ -98,7 +99,17 @@ func (a *WhisperServiceAdapter) createFilter(opts protocol.SubscribeOptions) (*w
|
|||||||
AllowP2P: true,
|
AllowP2P: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.IsPublic() {
|
if opts.Identity != nil {
|
||||||
|
filter.KeyAsym = opts.Identity
|
||||||
|
|
||||||
|
topic, err := PrivateChatTopic()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
filter.Topics = append(filter.Topics, topic[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.ChatName != "" {
|
||||||
symKeyID, err := a.shh.AddSymKeyFromPassword(opts.ChatName)
|
symKeyID, err := a.shh.AddSymKeyFromPassword(opts.ChatName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -107,21 +118,12 @@ func (a *WhisperServiceAdapter) createFilter(opts protocol.SubscribeOptions) (*w
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
filter.KeySym = symKey
|
||||||
|
|
||||||
topic, err := PublicChatTopic(opts.ChatName)
|
topic, err := PublicChatTopic(opts.ChatName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
filter.KeySym = symKey
|
|
||||||
filter.Topics = append(filter.Topics, topic[:])
|
|
||||||
} else {
|
|
||||||
filter.KeyAsym = opts.Identity
|
|
||||||
|
|
||||||
topic, err := PrivateChatTopic()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
filter.Topics = append(filter.Topics, topic[:])
|
filter.Topics = append(filter.Topics, topic[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,7 +159,17 @@ func (a *WhisperServiceAdapter) createNewMessage(data []byte, options protocol.S
|
|||||||
|
|
||||||
message = createWhisperNewMessage(data, keyID)
|
message = createWhisperNewMessage(data, keyID)
|
||||||
|
|
||||||
if options.IsPublic() {
|
if options.Recipient != nil {
|
||||||
|
message.PublicKey = crypto.FromECDSAPub(options.Recipient)
|
||||||
|
|
||||||
|
topic, err := PrivateChatTopic()
|
||||||
|
if err != nil {
|
||||||
|
return message, err
|
||||||
|
}
|
||||||
|
message.Topic = topic
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.ChatName != "" {
|
||||||
symKeyID, err := a.shh.AddSymKeyFromPassword(options.ChatName)
|
symKeyID, err := a.shh.AddSymKeyFromPassword(options.ChatName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return message, err
|
return message, err
|
||||||
@ -169,14 +181,6 @@ func (a *WhisperServiceAdapter) createNewMessage(data []byte, options protocol.S
|
|||||||
return message, err
|
return message, err
|
||||||
}
|
}
|
||||||
message.Topic = topic
|
message.Topic = topic
|
||||||
} else {
|
|
||||||
message.PublicKey = crypto.FromECDSAPub(options.Recipient)
|
|
||||||
|
|
||||||
topic, err := PrivateChatTopic()
|
|
||||||
if err != nil {
|
|
||||||
return message, err
|
|
||||||
}
|
|
||||||
message.Topic = topic
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@ -199,7 +203,11 @@ func (a *WhisperServiceAdapter) Request(ctx context.Context, options protocol.Re
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
_, err = a.requestMessages(ctx, req, true)
|
_, err = a.requestMessages(ctx, req, true)
|
||||||
|
|
||||||
|
log.Printf("[WhisperServiceAdapter::Request] took %s", time.Since(now))
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,12 +218,15 @@ func (a *WhisperServiceAdapter) selectAndAddMailServer() (string, error) {
|
|||||||
|
|
||||||
config := a.node.Config()
|
config := a.node.Config()
|
||||||
enode := randomItem(config.ClusterConfig.TrustedMailServers)
|
enode := randomItem(config.ClusterConfig.TrustedMailServers)
|
||||||
errCh := helpers.WaitForPeerAsync(
|
errCh := waitForPeerAsync(
|
||||||
a.node.GethNode().Server(),
|
a.node.GethNode().Server(),
|
||||||
enode,
|
enode,
|
||||||
p2p.PeerEventTypeAdd,
|
p2p.PeerEventTypeAdd,
|
||||||
time.Second*5,
|
time.Second*5,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
log.Printf("[WhisperServiceAdapter::selectAndAddMailServer] randomly selected %s node", enode)
|
||||||
|
|
||||||
if err := a.node.AddPeer(enode); err != nil {
|
if err := a.node.AddPeer(enode); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@ -246,6 +257,9 @@ func (a *WhisperServiceAdapter) requestMessages(ctx context.Context, req shhext.
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Printf("[WhisperServiceAdapter::requestMessages] response = %+v, err = %v", resp, err)
|
||||||
|
|
||||||
if resp.Error != nil {
|
if resp.Error != nil {
|
||||||
err = resp.Error
|
err = resp.Error
|
||||||
return
|
return
|
||||||
@ -276,14 +290,16 @@ func (a *WhisperServiceAdapter) createMessagesRequest(
|
|||||||
SymKeyID: mailSymKeyID,
|
SymKeyID: mailSymKeyID,
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.IsPublic() {
|
if params.Recipient != nil {
|
||||||
topic, err := PublicChatTopic(params.ChatName)
|
topic, err := PrivateChatTopic()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return req, err
|
return req, err
|
||||||
}
|
}
|
||||||
req.Topics = append(req.Topics, topic)
|
req.Topics = append(req.Topics, topic)
|
||||||
} else {
|
}
|
||||||
topic, err := PrivateChatTopic()
|
|
||||||
|
if params.ChatName != "" {
|
||||||
|
topic, err := PublicChatTopic(params.ChatName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return req, err
|
return req, err
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,14 @@ import (
|
|||||||
whisper "github.com/status-im/whisper/whisperv6"
|
whisper "github.com/status-im/whisper/whisperv6"
|
||||||
)
|
)
|
||||||
|
|
||||||
func randomItem(items []string) string {
|
// shhextRequestMessagesParam is used to remove dependency on shhext module.
|
||||||
l := len(items)
|
type shhextRequestMessagesParam struct {
|
||||||
return items[rand.Intn(l)]
|
MailServerPeer string `json:"mailServerPeer"`
|
||||||
|
From int64 `json:"from"`
|
||||||
|
To int64 `json:"to"`
|
||||||
|
Limit int `json:"limit"`
|
||||||
|
SymKeyID string `json:"symKeyID"`
|
||||||
|
Topics []whisper.TopicType `json:"topics"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func createWhisperNewMessage(data []byte, sigKey string) whisper.NewMessage {
|
func createWhisperNewMessage(data []byte, sigKey string) whisper.NewMessage {
|
||||||
@ -20,3 +25,8 @@ func createWhisperNewMessage(data []byte, sigKey string) whisper.NewMessage {
|
|||||||
Sig: sigKey,
|
Sig: sigKey,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func randomItem(items []string) string {
|
||||||
|
l := len(items)
|
||||||
|
return items[rand.Intn(l)]
|
||||||
|
}
|
||||||
|
@ -4,11 +4,10 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/status-im/status-console-client/protocol/v1"
|
"github.com/status-im/status-console-client/protocol/v1"
|
||||||
@ -36,7 +35,8 @@ type Chat struct {
|
|||||||
lastClock int64
|
lastClock int64
|
||||||
|
|
||||||
ownMessages chan *protocol.Message // my private messages channel
|
ownMessages chan *protocol.Message // my private messages channel
|
||||||
// TODO: make it a ring buffer
|
// TODO: make it a ring buffer. It will require loading newer messages
|
||||||
|
// from the cache as well.
|
||||||
messages []*protocol.Message // all messages ordered by Clock
|
messages []*protocol.Message // all messages ordered by Clock
|
||||||
messagesByHash map[string]*protocol.Message // quick access to messages by hash
|
messagesByHash map[string]*protocol.Message // quick access to messages by hash
|
||||||
}
|
}
|
||||||
@ -116,11 +116,9 @@ func (c *Chat) Subscribe(params protocol.RequestOptions) (err error) {
|
|||||||
return errors.New("already subscribed")
|
return errors.New("already subscribed")
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := protocol.SubscribeOptions{}
|
opts, err := extendSubscribeOptions(protocol.SubscribeOptions{}, c)
|
||||||
if c.contact.Type == ContactPublicChat {
|
if err != nil {
|
||||||
opts.ChatName = c.contact.Name
|
return errors.Wrap(err, "failed to subscribe")
|
||||||
} else {
|
|
||||||
opts.Identity = c.identity
|
|
||||||
}
|
}
|
||||||
|
|
||||||
messages := make(chan *protocol.Message)
|
messages := make(chan *protocol.Message)
|
||||||
@ -140,12 +138,12 @@ func (c *Chat) Subscribe(params protocol.RequestOptions) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load loads messages from the database cache and the network.
|
// Load loads messages from the database cache and the network.
|
||||||
func (c *Chat) load(params protocol.RequestOptions) error {
|
func (c *Chat) load(options protocol.RequestOptions) error {
|
||||||
// Get already cached messages from the database.
|
// Get already cached messages from the database.
|
||||||
cachedMessages, err := c.db.Messages(
|
cachedMessages, err := c.db.Messages(
|
||||||
c.contact,
|
c.contact,
|
||||||
params.From,
|
options.From,
|
||||||
params.To,
|
options.To,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "db failed to get messages")
|
return errors.Wrap(err, "db failed to get messages")
|
||||||
@ -160,21 +158,19 @@ func (c *Chat) load(params protocol.RequestOptions) error {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
// Request historic messages from the network.
|
// Request historic messages from the network.
|
||||||
if err := c.request(params); err != nil {
|
if err := c.request(options); err != nil {
|
||||||
return errors.Wrap(err, "failed to request for messages")
|
return errors.Wrap(err, "failed to request for messages")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Chat) request(params protocol.RequestOptions) error {
|
func (c *Chat) request(options protocol.RequestOptions) error {
|
||||||
if c.contact.Type == ContactPublicChat {
|
opts, err := extendRequestOptions(options, c)
|
||||||
params.ChatName = c.contact.Name
|
if err != nil {
|
||||||
} else {
|
return err
|
||||||
params.Recipient = c.contact.PublicKey
|
|
||||||
}
|
}
|
||||||
|
return c.proto.Request(context.Background(), opts)
|
||||||
return c.proto.Request(context.Background(), params)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request historic messages.
|
// Request historic messages.
|
||||||
@ -189,7 +185,7 @@ func (c *Chat) Send(data []byte) error {
|
|||||||
// This is needed to prevent sending messages
|
// This is needed to prevent sending messages
|
||||||
// if the chat is already left/canceled
|
// if the chat is already left/canceled
|
||||||
// as a it can't be guaranteed that processing
|
// as a it can't be guaranteed that processing
|
||||||
// looop goroutines are still running.
|
// loop goroutines are still running.
|
||||||
select {
|
select {
|
||||||
case _, ok := <-c.cancel:
|
case _, ok := <-c.cancel:
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -198,40 +194,31 @@ func (c *Chat) Send(data []byte) error {
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
var messageType string
|
var message protocol.StatusMessage
|
||||||
|
|
||||||
text := strings.TrimSpace(string(data))
|
switch c.contact.Type {
|
||||||
ts := time.Now().Unix() * 1000
|
case ContactPublicChat:
|
||||||
clock := protocol.CalcMessageClock(c.lastClock, ts)
|
message = protocol.CreatePublicTextMessage(data, c.lastClock, c.contact.Name)
|
||||||
opts := protocol.SendOptions{
|
case ContactPrivateChat:
|
||||||
Identity: c.identity,
|
message = protocol.CreatePrivateTextMessage(data, c.lastClock, c.contact.Name)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("failed to send message: unsupported contact type")
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.contact.Type == ContactPublicChat {
|
|
||||||
opts.ChatName = c.contact.Name
|
|
||||||
messageType = protocol.MessageTypePublicGroupUserMessage
|
|
||||||
} else {
|
|
||||||
opts.Recipient = c.contact.PublicKey
|
|
||||||
messageType = protocol.MessageTypePrivateUserMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
message := protocol.StatusMessage{
|
|
||||||
Text: text,
|
|
||||||
ContentT: protocol.ContentTypeTextPlain,
|
|
||||||
MessageT: messageType,
|
|
||||||
Clock: clock,
|
|
||||||
Timestamp: ts,
|
|
||||||
Content: protocol.StatusMessageContent{ChatID: c.contact.Name, Text: text},
|
|
||||||
}
|
|
||||||
encodedMessage, err := protocol.EncodeMessage(message)
|
encodedMessage, err := protocol.EncodeMessage(message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to encode message")
|
return errors.Wrap(err, "failed to encode message")
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Lock()
|
c.Lock()
|
||||||
c.updateLastClock(clock)
|
c.updateLastClock(message.Clock)
|
||||||
c.Unlock()
|
c.Unlock()
|
||||||
|
|
||||||
|
opts, err := extendSendOptions(protocol.SendOptions{Identity: c.identity}, c)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to prepare send options")
|
||||||
|
}
|
||||||
|
|
||||||
hash, err := c.proto.Send(context.Background(), encodedMessage, opts)
|
hash, err := c.proto.Send(context.Background(), encodedMessage, opts)
|
||||||
|
|
||||||
// Own messages need to be pushed manually to the pipeline.
|
// Own messages need to be pushed manually to the pipeline.
|
||||||
|
47
protocol/client/chat_options.go
Normal file
47
protocol/client/chat_options.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/status-im/status-console-client/protocol/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errUnsupportedContactType = fmt.Errorf("unsupported contact type")
|
||||||
|
)
|
||||||
|
|
||||||
|
func extendSubscribeOptions(opts protocol.SubscribeOptions, c *Chat) (protocol.SubscribeOptions, error) {
|
||||||
|
switch c.contact.Type {
|
||||||
|
case ContactPublicChat:
|
||||||
|
opts.ChatName = c.contact.Name
|
||||||
|
case ContactPrivateChat:
|
||||||
|
opts.Identity = c.identity
|
||||||
|
default:
|
||||||
|
return opts, errUnsupportedContactType
|
||||||
|
}
|
||||||
|
return opts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func extendRequestOptions(opts protocol.RequestOptions, c *Chat) (protocol.RequestOptions, error) {
|
||||||
|
switch c.contact.Type {
|
||||||
|
case ContactPublicChat:
|
||||||
|
opts.ChatName = c.contact.Name
|
||||||
|
case ContactPrivateChat:
|
||||||
|
opts.Recipient = c.contact.PublicKey
|
||||||
|
default:
|
||||||
|
return opts, errUnsupportedContactType
|
||||||
|
}
|
||||||
|
return opts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func extendSendOptions(opts protocol.SendOptions, c *Chat) (protocol.SendOptions, error) {
|
||||||
|
switch c.contact.Type {
|
||||||
|
case ContactPublicChat:
|
||||||
|
opts.ChatName = c.contact.Name
|
||||||
|
case ContactPrivateChat:
|
||||||
|
opts.Recipient = c.contact.PublicKey
|
||||||
|
default:
|
||||||
|
return opts, errUnsupportedContactType
|
||||||
|
}
|
||||||
|
return opts, nil
|
||||||
|
}
|
@ -55,7 +55,7 @@ func (m *ChatMock) Request(ctx context.Context, params protocol.RequestOptions)
|
|||||||
|
|
||||||
func TestSubscribe(t *testing.T) {
|
func TestSubscribe(t *testing.T) {
|
||||||
proto := ChatMock{}
|
proto := ChatMock{}
|
||||||
contact := Contact{}
|
contact := Contact{Name: "test", Type: ContactPublicChat}
|
||||||
|
|
||||||
db, err := NewDatabase("")
|
db, err := NewDatabase("")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -23,7 +23,7 @@ type Messenger struct {
|
|||||||
events chan interface{}
|
events chan interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMessanger returns a new Messanger.
|
// NewMessenger returns a new Messanger.
|
||||||
func NewMessenger(proto protocol.Chat, identity *ecdsa.PrivateKey, db *Database) *Messenger {
|
func NewMessenger(proto protocol.Chat, identity *ecdsa.PrivateKey, db *Database) *Messenger {
|
||||||
return &Messenger{
|
return &Messenger{
|
||||||
proto: proto,
|
proto: proto,
|
||||||
@ -50,15 +50,12 @@ func (m *Messenger) Chat(c Contact) *Chat {
|
|||||||
|
|
||||||
// Join creates a new chat and creates a subscription.
|
// Join creates a new chat and creates a subscription.
|
||||||
func (m *Messenger) Join(contact Contact, params protocol.RequestOptions) error {
|
func (m *Messenger) Join(contact Contact, params protocol.RequestOptions) error {
|
||||||
m.RLock()
|
chat := m.Chat(contact)
|
||||||
_, found := m.chats[contact]
|
if chat != nil {
|
||||||
m.RUnlock()
|
return chat.load(params)
|
||||||
|
|
||||||
if found {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
chat := NewChat(m.proto, m.identity, contact, m.db)
|
chat = NewChat(m.proto, m.identity, contact, m.db)
|
||||||
cancel := make(chan struct{})
|
cancel := make(chan struct{})
|
||||||
|
|
||||||
m.Lock()
|
m.Lock()
|
||||||
|
@ -55,9 +55,6 @@ func (o RequestOptions) Validate() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsPublic returns true if RequestOptions are for a public chat.
|
|
||||||
func (o RequestOptions) IsPublic() bool { return o.ChatName != "" }
|
|
||||||
|
|
||||||
// DefaultRequestOptions returns default options returning messages
|
// DefaultRequestOptions returns default options returning messages
|
||||||
// from the last 24 hours.
|
// from the last 24 hours.
|
||||||
func DefaultRequestOptions() RequestOptions {
|
func DefaultRequestOptions() RequestOptions {
|
||||||
@ -85,9 +82,6 @@ func (o SubscribeOptions) Validate() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsPublic returns true if SubscribeOptions are for a public chat.
|
|
||||||
func (o SubscribeOptions) IsPublic() bool { return o.ChatName != "" }
|
|
||||||
|
|
||||||
// SendOptions are options for Chat.Send.
|
// SendOptions are options for Chat.Send.
|
||||||
type SendOptions struct {
|
type SendOptions struct {
|
||||||
Identity *ecdsa.PrivateKey
|
Identity *ecdsa.PrivateKey
|
||||||
@ -109,6 +103,3 @@ func (o SendOptions) Validate() error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsPublic returns true if SendOptions are for a public chat.
|
|
||||||
func (o SendOptions) IsPublic() bool { return o.ChatName != "" }
|
|
||||||
|
@ -3,6 +3,8 @@ package protocol
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -38,6 +40,32 @@ type StatusMessage struct {
|
|||||||
Content StatusMessageContent
|
Content StatusMessageContent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateTextStatusMessage creates a StatusMessage.
|
||||||
|
func CreateTextStatusMessage(data []byte, lastClock int64, chatID, messageType string) StatusMessage {
|
||||||
|
text := strings.TrimSpace(string(data))
|
||||||
|
ts := time.Now().Unix() * 1000
|
||||||
|
clock := CalcMessageClock(lastClock, ts)
|
||||||
|
|
||||||
|
return StatusMessage{
|
||||||
|
Text: text,
|
||||||
|
ContentT: ContentTypeTextPlain,
|
||||||
|
MessageT: messageType,
|
||||||
|
Clock: clock,
|
||||||
|
Timestamp: ts,
|
||||||
|
Content: StatusMessageContent{ChatID: chatID, Text: text},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatePublicTextMessage creates a public text StatusMessage.
|
||||||
|
func CreatePublicTextMessage(data []byte, lastClock int64, chatID string) StatusMessage {
|
||||||
|
return CreateTextStatusMessage(data, lastClock, chatID, MessageTypePublicGroupUserMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatePrivateTextMessage creates a public text StatusMessage.
|
||||||
|
func CreatePrivateTextMessage(data []byte, lastClock int64, chatID string) StatusMessage {
|
||||||
|
return CreateTextStatusMessage(data, lastClock, chatID, MessageTypePrivateUserMessage)
|
||||||
|
}
|
||||||
|
|
||||||
// DecodeMessage decodes a raw payload to StatusMessage struct.
|
// DecodeMessage decodes a raw payload to StatusMessage struct.
|
||||||
func DecodeMessage(data []byte) (message StatusMessage, err error) {
|
func DecodeMessage(data []byte) (message StatusMessage, err error) {
|
||||||
buf := bytes.NewBuffer(data)
|
buf := bytes.NewBuffer(data)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user