2019-11-21 16:19:22 +00:00
|
|
|
package protocol
|
2019-07-17 22:25:42 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"crypto/ecdsa"
|
2020-07-14 14:07:19 +00:00
|
|
|
"database/sql"
|
2020-08-07 13:49:37 +00:00
|
|
|
"encoding/hex"
|
2020-05-13 13:16:17 +00:00
|
|
|
"io/ioutil"
|
2020-12-15 14:43:41 +00:00
|
|
|
"math"
|
2019-12-02 15:34:05 +00:00
|
|
|
"math/rand"
|
2020-05-13 13:16:17 +00:00
|
|
|
"os"
|
2020-07-07 13:55:24 +00:00
|
|
|
"reflect"
|
2019-12-02 15:34:05 +00:00
|
|
|
"sync"
|
2019-07-17 22:25:42 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
2019-09-02 09:29:06 +00:00
|
|
|
"go.uber.org/zap"
|
2019-07-17 22:25:42 +00:00
|
|
|
|
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
|
|
|
"github.com/golang/protobuf/proto"
|
2020-01-02 09:10:19 +00:00
|
|
|
|
2019-11-23 17:57:05 +00:00
|
|
|
"github.com/status-im/status-go/eth-node/crypto"
|
|
|
|
"github.com/status-im/status-go/eth-node/types"
|
|
|
|
enstypes "github.com/status-im/status-go/eth-node/types/ens"
|
2020-11-05 17:35:20 +00:00
|
|
|
userimage "github.com/status-im/status-go/images"
|
2020-06-17 18:55:49 +00:00
|
|
|
"github.com/status-im/status-go/protocol/audio"
|
2020-07-22 07:41:40 +00:00
|
|
|
"github.com/status-im/status-go/protocol/common"
|
2019-11-21 16:19:22 +00:00
|
|
|
"github.com/status-im/status-go/protocol/encryption"
|
|
|
|
"github.com/status-im/status-go/protocol/encryption/multidevice"
|
|
|
|
"github.com/status-im/status-go/protocol/encryption/sharedsecret"
|
|
|
|
"github.com/status-im/status-go/protocol/identity/alias"
|
|
|
|
"github.com/status-im/status-go/protocol/identity/identicon"
|
2020-05-13 13:16:17 +00:00
|
|
|
"github.com/status-im/status-go/protocol/images"
|
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
|
|
|
"github.com/status-im/status-go/protocol/protobuf"
|
2020-07-22 07:41:40 +00:00
|
|
|
"github.com/status-im/status-go/protocol/pushnotificationclient"
|
|
|
|
"github.com/status-im/status-go/protocol/pushnotificationserver"
|
2019-11-21 16:19:22 +00:00
|
|
|
"github.com/status-im/status-go/protocol/sqlite"
|
2020-01-13 19:17:30 +00:00
|
|
|
"github.com/status-im/status-go/protocol/transport"
|
|
|
|
wakutransp "github.com/status-im/status-go/protocol/transport/waku"
|
|
|
|
shhtransp "github.com/status-im/status-go/protocol/transport/whisper"
|
2019-11-21 16:19:22 +00:00
|
|
|
v1protocol "github.com/status-im/status-go/protocol/v1"
|
2019-07-17 22:25:42 +00:00
|
|
|
)
|
|
|
|
|
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
|
|
|
const PubKeyStringLength = 132
|
|
|
|
|
2020-02-10 11:22:37 +00:00
|
|
|
const transactionSentTxt = "Transaction sent"
|
|
|
|
|
2020-12-15 14:43:41 +00:00
|
|
|
const emojiResendMinDelay = 30
|
|
|
|
const emojiResendMaxCount = 3
|
|
|
|
|
2019-07-17 22:25:42 +00:00
|
|
|
// Messenger is a entity managing chats and messages.
|
|
|
|
// It acts as a bridge between the application and encryption
|
|
|
|
// layers.
|
|
|
|
// It needs to expose an interface to manage installations
|
|
|
|
// because installations are managed by the user.
|
|
|
|
// Similarly, it needs to expose an interface to manage
|
|
|
|
// mailservers because they can also be managed by the user.
|
|
|
|
type Messenger struct {
|
2019-12-02 15:34:05 +00:00
|
|
|
node types.Node
|
2020-07-31 09:46:38 +00:00
|
|
|
config *config
|
2019-12-02 15:34:05 +00:00
|
|
|
identity *ecdsa.PrivateKey
|
|
|
|
persistence *sqlitePersistence
|
2020-01-13 19:17:30 +00:00
|
|
|
transport transport.Transport
|
2019-12-02 15:34:05 +00:00
|
|
|
encryptor *encryption.Protocol
|
2020-07-06 08:54:22 +00:00
|
|
|
processor *common.MessageProcessor
|
2020-01-10 18:59:01 +00:00
|
|
|
handler *MessageHandler
|
2020-07-22 07:41:40 +00:00
|
|
|
pushNotificationClient *pushnotificationclient.Client
|
|
|
|
pushNotificationServer *pushnotificationserver.Server
|
2019-12-02 15:34:05 +00:00
|
|
|
logger *zap.Logger
|
2020-01-10 18:59:01 +00:00
|
|
|
verifyTransactionClient EthClient
|
2020-07-06 08:54:22 +00:00
|
|
|
featureFlags common.FeatureFlags
|
2019-07-17 22:25:42 +00:00
|
|
|
messagesPersistenceEnabled bool
|
2019-07-26 07:17:29 +00:00
|
|
|
shutdownTasks []func() error
|
2019-12-02 15:34:05 +00:00
|
|
|
systemMessagesTranslations map[protobuf.MembershipUpdateEvent_EventType]string
|
|
|
|
allChats map[string]*Chat
|
|
|
|
allContacts map[string]*Contact
|
2020-01-10 18:59:01 +00:00
|
|
|
allInstallations map[string]*multidevice.Installation
|
|
|
|
modifiedInstallations map[string]bool
|
|
|
|
installationID string
|
2020-07-07 09:00:04 +00:00
|
|
|
mailserver []byte
|
2020-07-14 14:07:19 +00:00
|
|
|
database *sql.DB
|
2020-08-27 12:38:59 +00:00
|
|
|
quit chan struct{}
|
2020-01-10 18:59:01 +00:00
|
|
|
|
|
|
|
mutex sync.Mutex
|
2019-07-17 22:25:42 +00:00
|
|
|
}
|
|
|
|
|
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
|
|
|
type RawResponse struct {
|
|
|
|
Filter *transport.Filter `json:"filter"`
|
|
|
|
Messages []*v1protocol.StatusMessage `json:"messages"`
|
|
|
|
}
|
|
|
|
|
2019-07-30 06:14:13 +00:00
|
|
|
type dbConfig struct {
|
|
|
|
dbPath string
|
|
|
|
dbKey string
|
|
|
|
}
|
|
|
|
|
2020-12-15 14:43:41 +00:00
|
|
|
type EnvelopeEventsInterceptor struct {
|
|
|
|
EnvelopeEventsHandler transport.EnvelopeEventsHandler
|
|
|
|
Messenger *Messenger
|
|
|
|
}
|
|
|
|
|
|
|
|
// EnvelopeSent triggered when envelope delivered at least to 1 peer.
|
|
|
|
func (interceptor EnvelopeEventsInterceptor) EnvelopeSent(identifiers [][]byte) {
|
|
|
|
if interceptor.Messenger != nil {
|
|
|
|
var ids []string
|
|
|
|
for _, identifierBytes := range identifiers {
|
|
|
|
ids = append(ids, types.EncodeHex(identifierBytes))
|
|
|
|
}
|
|
|
|
|
|
|
|
err := interceptor.Messenger.processSentMessages(ids)
|
|
|
|
if err != nil {
|
|
|
|
interceptor.Messenger.logger.Info("Messenger failed to process sent messages", zap.Error(err))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
interceptor.EnvelopeEventsHandler.EnvelopeSent(identifiers)
|
|
|
|
}
|
|
|
|
|
|
|
|
// EnvelopeExpired triggered when envelope is expired but wasn't delivered to any peer.
|
|
|
|
func (interceptor EnvelopeEventsInterceptor) EnvelopeExpired(identifiers [][]byte, err error) {
|
|
|
|
//we don't track expired events in Messenger, so just redirect to handler
|
|
|
|
interceptor.EnvelopeEventsHandler.EnvelopeExpired(identifiers, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// MailServerRequestCompleted triggered when the mailserver sends a message to notify that the request has been completed
|
|
|
|
func (interceptor EnvelopeEventsInterceptor) MailServerRequestCompleted(requestID types.Hash, lastEnvelopeHash types.Hash, cursor []byte, err error) {
|
|
|
|
//we don't track mailserver requests in Messenger, so just redirect to handler
|
|
|
|
interceptor.EnvelopeEventsHandler.MailServerRequestCompleted(requestID, lastEnvelopeHash, cursor, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// MailServerRequestExpired triggered when the mailserver request expires
|
|
|
|
func (interceptor EnvelopeEventsInterceptor) MailServerRequestExpired(hash types.Hash) {
|
|
|
|
//we don't track mailserver requests in Messenger, so just redirect to handler
|
|
|
|
interceptor.EnvelopeEventsHandler.MailServerRequestExpired(hash)
|
|
|
|
}
|
|
|
|
|
2019-07-17 22:25:42 +00:00
|
|
|
func NewMessenger(
|
|
|
|
identity *ecdsa.PrivateKey,
|
2019-11-23 17:57:05 +00:00
|
|
|
node types.Node,
|
2019-07-17 22:25:42 +00:00
|
|
|
installationID string,
|
|
|
|
opts ...Option,
|
|
|
|
) (*Messenger, error) {
|
|
|
|
var messenger *Messenger
|
|
|
|
|
|
|
|
c := config{}
|
|
|
|
|
|
|
|
for _, opt := range opts {
|
|
|
|
if err := opt(&c); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
logger := c.logger
|
|
|
|
if c.logger == nil {
|
|
|
|
var err error
|
|
|
|
if logger, err = zap.NewDevelopment(); err != nil {
|
|
|
|
return nil, errors.Wrap(err, "failed to create a logger")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-02 15:34:05 +00:00
|
|
|
if c.systemMessagesTranslations == nil {
|
|
|
|
c.systemMessagesTranslations = defaultSystemMessagesTranslations
|
|
|
|
}
|
|
|
|
|
2019-07-30 06:14:13 +00:00
|
|
|
// Configure the database.
|
|
|
|
database := c.db
|
2019-09-26 09:26:33 +00:00
|
|
|
if c.db == nil && c.dbConfig == (dbConfig{}) {
|
|
|
|
return nil, errors.New("database instance or database path needs to be provided")
|
|
|
|
}
|
|
|
|
if c.db == nil {
|
2019-07-30 06:14:13 +00:00
|
|
|
logger.Info("opening a database", zap.String("dbPath", c.dbConfig.dbPath))
|
2019-09-26 09:26:33 +00:00
|
|
|
var err error
|
2019-07-30 06:14:13 +00:00
|
|
|
database, err = sqlite.Open(c.dbConfig.dbPath, c.dbConfig.dbKey)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "failed to initialize database from the db config")
|
|
|
|
}
|
|
|
|
}
|
2019-09-26 09:26:33 +00:00
|
|
|
|
2019-07-30 06:14:13 +00:00
|
|
|
// Apply migrations for all components.
|
2020-01-13 19:17:30 +00:00
|
|
|
err := sqlite.Migrate(database)
|
2019-07-30 06:14:13 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "failed to apply migrations")
|
2019-07-17 22:25:42 +00:00
|
|
|
}
|
|
|
|
|
2019-07-30 06:14:13 +00:00
|
|
|
// Initialize transport layer.
|
2020-01-13 19:17:30 +00:00
|
|
|
var transp transport.Transport
|
2020-01-20 20:56:06 +00:00
|
|
|
if shh, err := node.GetWhisper(nil); err == nil && shh != nil {
|
2020-02-10 11:22:37 +00:00
|
|
|
transp, err = shhtransp.NewTransport(
|
2020-01-13 19:17:30 +00:00
|
|
|
shh,
|
|
|
|
identity,
|
|
|
|
database,
|
|
|
|
nil,
|
|
|
|
c.envelopesMonitorConfig,
|
|
|
|
logger,
|
|
|
|
)
|
|
|
|
if err != nil {
|
2020-02-10 11:22:37 +00:00
|
|
|
return nil, errors.Wrap(err, "failed to create Transport")
|
2020-01-13 19:17:30 +00:00
|
|
|
}
|
2020-01-20 20:56:06 +00:00
|
|
|
} else {
|
2020-01-13 19:17:30 +00:00
|
|
|
logger.Info("failed to find Whisper service; trying Waku", zap.Error(err))
|
|
|
|
waku, err := node.GetWaku(nil)
|
2020-01-20 20:56:06 +00:00
|
|
|
if err != nil || waku == nil {
|
2020-01-13 19:17:30 +00:00
|
|
|
return nil, errors.Wrap(err, "failed to find Whisper and Waku services")
|
|
|
|
}
|
2020-02-10 11:22:37 +00:00
|
|
|
transp, err = wakutransp.NewTransport(
|
2020-01-13 19:17:30 +00:00
|
|
|
waku,
|
|
|
|
identity,
|
|
|
|
database,
|
|
|
|
nil,
|
|
|
|
c.envelopesMonitorConfig,
|
|
|
|
logger,
|
|
|
|
)
|
|
|
|
if err != nil {
|
2020-02-10 11:22:37 +00:00
|
|
|
return nil, errors.Wrap(err, "failed to create Transport")
|
2020-01-13 19:17:30 +00:00
|
|
|
}
|
2019-07-17 22:25:42 +00:00
|
|
|
}
|
|
|
|
|
2019-07-30 06:14:13 +00:00
|
|
|
// Initialize encryption layer.
|
|
|
|
encryptionProtocol := encryption.New(
|
|
|
|
database,
|
2019-07-17 22:25:42 +00:00
|
|
|
installationID,
|
|
|
|
logger,
|
|
|
|
)
|
2019-07-26 07:17:29 +00:00
|
|
|
|
2020-07-06 08:54:22 +00:00
|
|
|
processor, err := common.NewMessageProcessor(
|
2019-09-02 09:29:06 +00:00
|
|
|
identity,
|
2019-08-27 12:04:15 +00:00
|
|
|
database,
|
2019-09-02 09:29:06 +00:00
|
|
|
encryptionProtocol,
|
2020-01-13 19:17:30 +00:00
|
|
|
transp,
|
2019-08-29 06:33:46 +00:00
|
|
|
logger,
|
2019-09-02 09:29:06 +00:00
|
|
|
c.featureFlags,
|
2019-07-26 07:17:29 +00:00
|
|
|
)
|
2019-08-27 12:04:15 +00:00
|
|
|
if err != nil {
|
2019-09-02 09:29:06 +00:00
|
|
|
return nil, errors.Wrap(err, "failed to create messageProcessor")
|
2019-08-27 12:04:15 +00:00
|
|
|
}
|
|
|
|
|
2020-07-22 07:41:40 +00:00
|
|
|
// Initialize push notification server
|
|
|
|
var pushNotificationServer *pushnotificationserver.Server
|
2020-08-20 07:26:00 +00:00
|
|
|
if c.pushNotificationServerConfig != nil && c.pushNotificationServerConfig.Enabled {
|
2020-07-14 14:07:19 +00:00
|
|
|
c.pushNotificationServerConfig.Identity = identity
|
2020-07-22 07:41:40 +00:00
|
|
|
pushNotificationServerPersistence := pushnotificationserver.NewSQLitePersistence(database)
|
|
|
|
pushNotificationServer = pushnotificationserver.New(c.pushNotificationServerConfig, pushNotificationServerPersistence, processor)
|
2020-07-06 08:54:22 +00:00
|
|
|
}
|
|
|
|
|
2020-07-22 07:41:40 +00:00
|
|
|
// Initialize push notification client
|
|
|
|
pushNotificationClientPersistence := pushnotificationclient.NewPersistence(database)
|
2020-07-15 12:25:01 +00:00
|
|
|
pushNotificationClientConfig := c.pushNotificationClientConfig
|
|
|
|
if pushNotificationClientConfig == nil {
|
2020-07-22 07:41:40 +00:00
|
|
|
pushNotificationClientConfig = &pushnotificationclient.Config{}
|
2020-07-07 13:55:24 +00:00
|
|
|
}
|
2020-07-15 12:25:01 +00:00
|
|
|
|
2020-07-22 07:41:40 +00:00
|
|
|
// Overriding until we handle different identities
|
2020-07-15 12:25:01 +00:00
|
|
|
pushNotificationClientConfig.Identity = identity
|
|
|
|
pushNotificationClientConfig.Logger = logger
|
|
|
|
pushNotificationClientConfig.InstallationID = installationID
|
|
|
|
|
2020-09-02 14:11:16 +00:00
|
|
|
pushNotificationClient := pushnotificationclient.New(pushNotificationClientPersistence, pushNotificationClientConfig, processor, &sqlitePersistence{db: database})
|
2020-07-06 08:54:22 +00:00
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
handler := newMessageHandler(identity, logger, &sqlitePersistence{db: database})
|
|
|
|
|
2019-07-17 22:25:42 +00:00
|
|
|
messenger = &Messenger{
|
2020-07-31 09:46:38 +00:00
|
|
|
config: &c,
|
2019-11-23 17:57:05 +00:00
|
|
|
node: node,
|
2019-07-17 22:25:42 +00:00
|
|
|
identity: identity,
|
2019-07-30 06:14:13 +00:00
|
|
|
persistence: &sqlitePersistence{db: database},
|
2020-01-13 19:17:30 +00:00
|
|
|
transport: transp,
|
2019-07-17 22:25:42 +00:00
|
|
|
encryptor: encryptionProtocol,
|
2019-09-02 09:29:06 +00:00
|
|
|
processor: processor,
|
2020-01-10 18:59:01 +00:00
|
|
|
handler: handler,
|
2020-06-30 08:30:58 +00:00
|
|
|
pushNotificationClient: pushNotificationClient,
|
2020-07-03 10:08:47 +00:00
|
|
|
pushNotificationServer: pushNotificationServer,
|
2019-07-17 22:25:42 +00:00
|
|
|
featureFlags: c.featureFlags,
|
2019-12-02 15:34:05 +00:00
|
|
|
systemMessagesTranslations: c.systemMessagesTranslations,
|
|
|
|
allChats: make(map[string]*Chat),
|
|
|
|
allContacts: make(map[string]*Contact),
|
2020-01-10 18:59:01 +00:00
|
|
|
allInstallations: make(map[string]*multidevice.Installation),
|
|
|
|
installationID: installationID,
|
|
|
|
modifiedInstallations: make(map[string]bool),
|
2019-07-17 22:25:42 +00:00
|
|
|
messagesPersistenceEnabled: c.messagesPersistenceEnabled,
|
2020-01-10 18:59:01 +00:00
|
|
|
verifyTransactionClient: c.verifyTransactionClient,
|
2020-07-14 14:07:19 +00:00
|
|
|
database: database,
|
2020-08-27 12:38:59 +00:00
|
|
|
quit: make(chan struct{}),
|
2019-07-17 22:25:42 +00:00
|
|
|
shutdownTasks: []func() error{
|
2019-07-30 06:14:13 +00:00
|
|
|
database.Close,
|
2020-07-10 13:26:06 +00:00
|
|
|
pushNotificationClient.Stop,
|
2020-07-31 09:08:09 +00:00
|
|
|
encryptionProtocol.Stop,
|
2020-01-13 19:17:30 +00:00
|
|
|
transp.ResetFilters,
|
|
|
|
transp.Stop,
|
2019-09-02 09:29:06 +00:00
|
|
|
func() error { processor.Stop(); return nil },
|
2019-07-17 22:25:42 +00:00
|
|
|
// Currently this often fails, seems like it's safe to ignore them
|
|
|
|
// https://github.com/uber-go/zap/issues/328
|
|
|
|
func() error { _ = logger.Sync; return nil },
|
|
|
|
},
|
|
|
|
logger: logger,
|
|
|
|
}
|
|
|
|
|
2020-12-15 14:43:41 +00:00
|
|
|
if c.envelopesMonitorConfig != nil {
|
|
|
|
interceptor := EnvelopeEventsInterceptor{c.envelopesMonitorConfig.EnvelopeEventsHandler, messenger}
|
|
|
|
err := messenger.transport.SetEnvelopeEventsHandler(interceptor)
|
|
|
|
if err != nil {
|
|
|
|
logger.Info("Unable to set envelopes event handler", zap.Error(err))
|
|
|
|
}
|
|
|
|
}
|
2019-07-17 22:25:42 +00:00
|
|
|
|
2020-12-15 14:43:41 +00:00
|
|
|
logger.Debug("messages persistence", zap.Bool("enabled", c.messagesPersistenceEnabled))
|
2019-07-17 22:25:42 +00:00
|
|
|
return messenger, nil
|
|
|
|
}
|
|
|
|
|
2020-12-15 14:43:41 +00:00
|
|
|
func (m *Messenger) processSentMessages(ids []string) error {
|
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
|
|
|
|
for _, id := range ids {
|
|
|
|
rawMessage, err := m.persistence.RawMessageByID(id)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "Can't get raw message with id %v", id)
|
|
|
|
}
|
|
|
|
|
|
|
|
rawMessage.Sent = true
|
|
|
|
|
|
|
|
err = m.persistence.SaveRawMessage(rawMessage)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "Can't save raw message marked as sent")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func shouldResendEmojiReaction(message *common.RawMessage, t TimeSource) (bool, error) {
|
|
|
|
if message.MessageType != protobuf.ApplicationMetadataMessage_EMOJI_REACTION {
|
|
|
|
return false, errors.New("Should resend only emoji reactions")
|
|
|
|
}
|
|
|
|
|
|
|
|
if message.Sent {
|
|
|
|
return false, errors.New("Should resend only non-sent messages")
|
|
|
|
}
|
|
|
|
|
|
|
|
if message.SendCount > emojiResendMaxCount {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
//exponential backoff depends on how many attempts to send message already made
|
|
|
|
backoff := uint64(math.Pow(2, float64(message.SendCount-1))) * emojiResendMinDelay * uint64(time.Second)
|
|
|
|
backoffElapsed := t.GetCurrentTime() > (message.LastSent + backoff)
|
|
|
|
return backoffElapsed, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Messenger) resendExpiredEmojiReactions() error {
|
|
|
|
ids, err := m.persistence.ExpiredEmojiReactionsIDs(emojiResendMaxCount)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "Can't get expired reactions from db")
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, id := range ids {
|
|
|
|
rawMessage, err := m.persistence.RawMessageByID(id)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "Can't get raw message with id %v", id)
|
|
|
|
}
|
|
|
|
|
|
|
|
if ok, err := shouldResendEmojiReaction(rawMessage, m.getTimesource()); ok {
|
|
|
|
err = m.persistence.SaveRawMessage(rawMessage)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "Can't save raw message marked as non-expired")
|
|
|
|
}
|
|
|
|
|
|
|
|
err = m.reSendRawMessage(context.Background(), rawMessage.ID)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "Can't resend expired message with id %v", rawMessage.ID)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-01-29 19:40:06 +00:00
|
|
|
func (m *Messenger) Start() error {
|
2020-07-14 14:07:19 +00:00
|
|
|
m.logger.Info("starting messenger", zap.String("identity", types.EncodeHex(crypto.FromECDSAPub(&m.identity.PublicKey))))
|
2020-07-10 13:26:06 +00:00
|
|
|
// Start push notification server
|
|
|
|
if m.pushNotificationServer != nil {
|
|
|
|
if err := m.pushNotificationServer.Start(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2020-07-22 07:41:40 +00:00
|
|
|
|
|
|
|
// Start push notification client
|
2020-07-10 13:26:06 +00:00
|
|
|
if m.pushNotificationClient != nil {
|
2020-08-18 15:07:48 +00:00
|
|
|
m.handlePushNotificationClientRegistrations(m.pushNotificationClient.SubscribeToRegistrations())
|
|
|
|
|
2020-07-10 13:26:06 +00:00
|
|
|
if err := m.pushNotificationClient.Start(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-31 12:22:05 +00:00
|
|
|
// set shared secret handles
|
|
|
|
m.processor.SetHandleSharedSecrets(m.handleSharedSecrets)
|
|
|
|
|
2020-07-31 09:08:09 +00:00
|
|
|
subscriptions, err := m.encryptor.Start(m.identity)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-07-31 12:22:05 +00:00
|
|
|
|
|
|
|
// handle stored shared secrets
|
|
|
|
err = m.handleSharedSecrets(subscriptions.SharedSecrets)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:08:09 +00:00
|
|
|
m.handleEncryptionLayerSubscriptions(subscriptions)
|
2020-08-27 12:38:59 +00:00
|
|
|
m.handleConnectionChange(m.online())
|
|
|
|
m.watchConnectionChange()
|
2020-12-15 14:43:41 +00:00
|
|
|
m.watchExpiredEmojis()
|
2020-07-31 09:08:09 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-08-27 12:38:59 +00:00
|
|
|
// handle connection change is called each time we go from offline/online or viceversa
|
|
|
|
func (m *Messenger) handleConnectionChange(online bool) {
|
|
|
|
if online {
|
|
|
|
if m.pushNotificationClient != nil {
|
|
|
|
m.pushNotificationClient.Online()
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if m.pushNotificationClient != nil {
|
|
|
|
m.pushNotificationClient.Offline()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Messenger) online() bool {
|
|
|
|
return m.node.PeersCount() > 0
|
|
|
|
}
|
|
|
|
|
2020-08-18 15:07:48 +00:00
|
|
|
func (m *Messenger) buildContactCodeAdvertisement() (*protobuf.ContactCodeAdvertisement, error) {
|
|
|
|
if m.pushNotificationClient == nil || !m.pushNotificationClient.Enabled() {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
m.logger.Debug("adding push notification info to contact code bundle")
|
|
|
|
info, err := m.pushNotificationClient.MyPushNotificationQueryInfo()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if len(info) == 0 {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
return &protobuf.ContactCodeAdvertisement{
|
|
|
|
PushNotificationInfo: info,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2020-07-31 12:22:05 +00:00
|
|
|
// handleSendContactCode sends a public message wrapped in the encryption
|
|
|
|
// layer, which will propagate our bundle
|
|
|
|
func (m *Messenger) handleSendContactCode() error {
|
2020-07-31 13:46:27 +00:00
|
|
|
var payload []byte
|
2020-08-18 15:07:48 +00:00
|
|
|
m.logger.Debug("sending contact code")
|
|
|
|
contactCodeAdvertisement, err := m.buildContactCodeAdvertisement()
|
|
|
|
if err != nil {
|
|
|
|
m.logger.Error("could not build contact code advertisement", zap.Error(err))
|
|
|
|
}
|
|
|
|
|
2020-11-05 17:35:20 +00:00
|
|
|
err = m.handleContactCodeChatIdentity(contactCodeAdvertisement)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-08-18 15:07:48 +00:00
|
|
|
if contactCodeAdvertisement != nil {
|
|
|
|
payload, err = proto.Marshal(contactCodeAdvertisement)
|
2020-07-31 13:46:27 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-31 12:22:05 +00:00
|
|
|
contactCodeTopic := transport.ContactCodeTopic(&m.identity.PublicKey)
|
|
|
|
rawMessage := common.RawMessage{
|
|
|
|
LocalChatID: contactCodeTopic,
|
2020-08-18 15:07:48 +00:00
|
|
|
MessageType: protobuf.ApplicationMetadataMessage_CONTACT_CODE_ADVERTISEMENT,
|
2020-07-31 13:46:27 +00:00
|
|
|
Payload: payload,
|
2020-07-31 12:22:05 +00:00
|
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
|
|
defer cancel()
|
2020-08-18 15:07:48 +00:00
|
|
|
_, err = m.processor.SendPublic(ctx, contactCodeTopic, rawMessage)
|
2020-07-31 12:22:05 +00:00
|
|
|
if err != nil {
|
|
|
|
m.logger.Warn("failed to send a contact code", zap.Error(err))
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-11-05 17:35:20 +00:00
|
|
|
// contactCodeAdvertisement attaches a protobuf.ChatIdentity to the given protobuf.ContactCodeAdvertisement,
|
2020-11-09 15:16:36 +00:00
|
|
|
// if the `shouldPublish` conditions are met
|
2020-11-05 17:35:20 +00:00
|
|
|
func (m *Messenger) handleContactCodeChatIdentity(cca *protobuf.ContactCodeAdvertisement) error {
|
2020-11-09 15:16:36 +00:00
|
|
|
contactCodeTopic := transport.ContactCodeTopic(&m.identity.PublicKey)
|
|
|
|
shouldPublish, err := m.shouldPublishChatIdentity(contactCodeTopic)
|
2020-11-05 17:35:20 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-11-09 15:16:36 +00:00
|
|
|
if shouldPublish {
|
|
|
|
cca.ChatIdentity, err = m.createChatIdentity("private-chat")
|
2020-11-05 17:35:20 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-11-09 15:16:36 +00:00
|
|
|
err = m.persistence.SaveWhenChatIdentityLastPublished(contactCodeTopic)
|
2020-11-05 17:35:20 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-11-09 15:16:36 +00:00
|
|
|
// handleStandaloneChatIdentity sends a standalone ChatIdentity message to a public channel if the publish criteria is met
|
|
|
|
func (m *Messenger) handleStandaloneChatIdentity(chat *Chat) error {
|
|
|
|
if chat.ChatType != ChatTypePublic {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
shouldPublishChatIdentity, err := m.shouldPublishChatIdentity(chat.ID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if !shouldPublishChatIdentity {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
ci, err := m.createChatIdentity("public-chat")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-11-05 17:35:20 +00:00
|
|
|
|
2020-11-09 15:16:36 +00:00
|
|
|
payload, err := proto.Marshal(ci)
|
2020-11-05 17:35:20 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-11-09 15:16:36 +00:00
|
|
|
rawMessage := common.RawMessage{
|
|
|
|
LocalChatID: chat.ID,
|
|
|
|
MessageType: protobuf.ApplicationMetadataMessage_CHAT_IDENTITY,
|
|
|
|
Payload: payload,
|
|
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
|
|
defer cancel()
|
|
|
|
_, err = m.processor.SendPublic(ctx, chat.ID, rawMessage)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2020-11-05 17:35:20 +00:00
|
|
|
}
|
|
|
|
|
2020-11-09 15:16:36 +00:00
|
|
|
err = m.persistence.SaveWhenChatIdentityLastPublished(chat.ID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// shouldPublishChatIdentity returns true if the last time the ChatIdentity was attached was more than 24 hours ago
|
|
|
|
func (m *Messenger) shouldPublishChatIdentity(chatId string) (bool, error) {
|
|
|
|
lp, err := m.persistence.GetWhenChatIdentityLastPublished(chatId)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return *lp == 0 || time.Now().Unix() - *lp > 24 * 60 * 60, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// createChatIdentity creates a context based protobuf.ChatIdentity.
|
|
|
|
// context 'public-chat' will attach only the 'thumbnail' IdentityImage
|
|
|
|
// contect 'private-chat' will attach all IdentityImage
|
|
|
|
func (m *Messenger) createChatIdentity(context string) (*protobuf.ChatIdentity, error) {
|
|
|
|
ci := &protobuf.ChatIdentity{
|
2020-11-05 17:35:20 +00:00
|
|
|
Clock: m.transport.GetCurrentTime(),
|
|
|
|
EnsName: "", // TODO add ENS name handling to dedicate PR
|
|
|
|
}
|
|
|
|
|
2020-11-09 15:16:36 +00:00
|
|
|
ciis := make(map[string]*protobuf.IdentityImage)
|
|
|
|
|
|
|
|
switch context {
|
|
|
|
case "public-chat":
|
|
|
|
idb := userimage.NewDatabase(m.database)
|
|
|
|
img, err := idb.GetIdentityImage(userimage.SmallDimName)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
ciis[userimage.SmallDimName] = m.adaptIdentityImageToProtobuf(img)
|
|
|
|
ci.Images = ciis
|
|
|
|
|
|
|
|
case "private-chat":
|
|
|
|
idb := userimage.NewDatabase(m.database)
|
|
|
|
imgs, err := idb.GetIdentityImages()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, img := range imgs {
|
|
|
|
ciis[img.Name] = m.adaptIdentityImageToProtobuf(img)
|
|
|
|
}
|
|
|
|
ci.Images = ciis
|
|
|
|
}
|
|
|
|
|
|
|
|
return ci, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// adaptIdentityImageToProtobuf Adapts a images.IdentityImage to protobuf.IdentityImage
|
|
|
|
func (m *Messenger) adaptIdentityImageToProtobuf(img *userimage.IdentityImage) *protobuf.IdentityImage {
|
|
|
|
return &protobuf.IdentityImage{
|
|
|
|
Payload: img.Payload,
|
|
|
|
SourceType: protobuf.IdentityImage_RAW_PAYLOAD, // TODO add ENS avatar handling to dedicated PR
|
|
|
|
ImageType: images.ImageType(img.Payload),
|
|
|
|
}
|
2020-11-05 17:35:20 +00:00
|
|
|
}
|
|
|
|
|
2020-07-31 12:22:05 +00:00
|
|
|
// handleSharedSecrets process the negotiated secrets received from the encryption layer
|
|
|
|
func (m *Messenger) handleSharedSecrets(secrets []*sharedsecret.Secret) error {
|
2020-07-31 09:46:38 +00:00
|
|
|
var result []*transport.Filter
|
|
|
|
for _, secret := range secrets {
|
|
|
|
fSecret := types.NegotiatedSecret{
|
|
|
|
PublicKey: secret.Identity,
|
|
|
|
Key: secret.Key,
|
|
|
|
}
|
|
|
|
filter, err := m.transport.ProcessNegotiatedSecret(fSecret)
|
|
|
|
if err != nil {
|
2020-07-31 12:22:05 +00:00
|
|
|
return err
|
2020-07-31 09:46:38 +00:00
|
|
|
}
|
|
|
|
result = append(result, filter)
|
|
|
|
}
|
2020-07-31 12:22:05 +00:00
|
|
|
if m.config.onNegotiatedFilters != nil {
|
|
|
|
m.config.onNegotiatedFilters(result)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2020-07-31 09:46:38 +00:00
|
|
|
}
|
|
|
|
|
2020-07-31 12:22:05 +00:00
|
|
|
// handleInstallations adds the installations in the installations map
|
|
|
|
func (m *Messenger) handleInstallations(installations []*multidevice.Installation) {
|
2020-07-31 09:08:09 +00:00
|
|
|
for _, installation := range installations {
|
|
|
|
if installation.Identity == contactIDFromPublicKey(&m.identity.PublicKey) {
|
|
|
|
if _, ok := m.allInstallations[installation.ID]; !ok {
|
|
|
|
m.allInstallations[installation.ID] = installation
|
|
|
|
m.modifiedInstallations[installation.ID] = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:46:38 +00:00
|
|
|
// handleEncryptionLayerSubscriptions handles events from the encryption layer
|
2020-07-31 09:08:09 +00:00
|
|
|
func (m *Messenger) handleEncryptionLayerSubscriptions(subscriptions *encryption.Subscriptions) {
|
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
select {
|
2020-07-31 12:22:05 +00:00
|
|
|
case <-subscriptions.SendContactCode:
|
|
|
|
if err := m.handleSendContactCode(); err != nil {
|
|
|
|
m.logger.Error("failed to publish contact code", zap.Error(err))
|
2020-07-31 09:46:38 +00:00
|
|
|
}
|
|
|
|
|
2020-07-31 09:08:09 +00:00
|
|
|
case <-subscriptions.Quit:
|
|
|
|
m.logger.Debug("quitting encryption subscription loop")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
2020-01-29 19:40:06 +00:00
|
|
|
}
|
|
|
|
|
2020-08-27 12:38:59 +00:00
|
|
|
// watchConnectionChange checks the connection status and call handleConnectionChange when this changes
|
|
|
|
func (m *Messenger) watchConnectionChange() {
|
|
|
|
m.logger.Debug("watching connection changes")
|
|
|
|
state := m.online()
|
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-time.After(200 * time.Millisecond):
|
|
|
|
newState := m.online()
|
|
|
|
if state != newState {
|
|
|
|
state = newState
|
|
|
|
m.logger.Debug("connection changed", zap.Bool("online", state))
|
|
|
|
m.handleConnectionChange(state)
|
|
|
|
}
|
|
|
|
case <-m.quit:
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2020-12-15 14:43:41 +00:00
|
|
|
}()
|
|
|
|
}
|
2020-08-27 12:38:59 +00:00
|
|
|
|
2020-12-15 14:43:41 +00:00
|
|
|
// watchExpiredEmojis regularly checks for expired emojis and invoke their resending
|
|
|
|
func (m *Messenger) watchExpiredEmojis() {
|
|
|
|
m.logger.Debug("watching expired emojis")
|
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-time.After(time.Second):
|
|
|
|
if m.online() {
|
|
|
|
err := m.resendExpiredEmojiReactions()
|
|
|
|
if err != nil {
|
|
|
|
m.logger.Debug("Error when resending expired emoji reactions", zap.Error(err))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case <-m.quit:
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2020-08-27 12:38:59 +00:00
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
2020-08-18 15:07:48 +00:00
|
|
|
// handlePushNotificationClientRegistration handles registration events
|
|
|
|
func (m *Messenger) handlePushNotificationClientRegistrations(c chan struct{}) {
|
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
_, more := <-c
|
|
|
|
if !more {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err := m.handleSendContactCode(); err != nil {
|
|
|
|
m.logger.Error("failed to publish contact code", zap.Error(err))
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
2019-08-29 06:33:46 +00:00
|
|
|
// Init analyzes chats and contacts in order to setup filters
|
|
|
|
// which are responsible for retrieving messages.
|
|
|
|
func (m *Messenger) Init() error {
|
2019-12-02 15:34:05 +00:00
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
|
|
|
|
// Seed the for color generation
|
|
|
|
rand.Seed(time.Now().Unix())
|
|
|
|
|
2019-08-29 06:33:46 +00:00
|
|
|
logger := m.logger.With(zap.String("site", "Init"))
|
|
|
|
|
|
|
|
var (
|
|
|
|
publicChatIDs []string
|
|
|
|
publicKeys []*ecdsa.PublicKey
|
|
|
|
)
|
|
|
|
|
|
|
|
// Get chat IDs and public keys from the existing chats.
|
|
|
|
// TODO: Get only active chats by the query.
|
2019-12-02 15:34:05 +00:00
|
|
|
chats, err := m.persistence.Chats()
|
2019-08-29 06:33:46 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for _, chat := range chats {
|
2020-02-07 11:56:30 +00:00
|
|
|
if err := chat.Validate(); err != nil {
|
|
|
|
logger.Warn("failed to validate chat", zap.Error(err))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2019-12-02 15:34:05 +00:00
|
|
|
m.allChats[chat.ID] = chat
|
2020-11-06 10:57:05 +00:00
|
|
|
if !chat.Active || chat.Timeline() {
|
2019-08-29 06:33:46 +00:00
|
|
|
continue
|
|
|
|
}
|
2020-11-06 10:57:05 +00:00
|
|
|
|
2019-08-29 06:33:46 +00:00
|
|
|
switch chat.ChatType {
|
2020-10-20 15:10:28 +00:00
|
|
|
case ChatTypePublic, ChatTypeProfile:
|
2019-08-29 06:33:46 +00:00
|
|
|
publicChatIDs = append(publicChatIDs, chat.ID)
|
|
|
|
case ChatTypeOneToOne:
|
2019-12-02 15:34:05 +00:00
|
|
|
pk, err := chat.PublicKey()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
publicKeys = append(publicKeys, pk)
|
2019-08-29 06:33:46 +00:00
|
|
|
case ChatTypePrivateGroupChat:
|
|
|
|
for _, member := range chat.Members {
|
|
|
|
publicKey, err := member.PublicKey()
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "invalid public key for member %s in chat %s", member.ID, chat.Name)
|
|
|
|
}
|
|
|
|
publicKeys = append(publicKeys, publicKey)
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return errors.New("invalid chat type")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get chat IDs and public keys from the contacts.
|
2019-12-02 15:34:05 +00:00
|
|
|
contacts, err := m.persistence.Contacts()
|
2019-08-29 06:33:46 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for _, contact := range contacts {
|
2019-12-02 15:34:05 +00:00
|
|
|
m.allContacts[contact.ID] = contact
|
2019-08-29 06:33:46 +00:00
|
|
|
// We only need filters for contacts added by us and not blocked.
|
|
|
|
if !contact.IsAdded() || contact.IsBlocked() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
publicKey, err := contact.PublicKey()
|
|
|
|
if err != nil {
|
|
|
|
logger.Error("failed to get contact's public key", zap.Error(err))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
publicKeys = append(publicKeys, publicKey)
|
|
|
|
}
|
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
installations, err := m.encryptor.GetOurInstallations(&m.identity.PublicKey)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, installation := range installations {
|
|
|
|
m.allInstallations[installation.ID] = installation
|
|
|
|
}
|
|
|
|
|
2019-09-02 09:29:06 +00:00
|
|
|
_, err = m.transport.InitFilters(publicChatIDs, publicKeys)
|
2019-08-29 06:33:46 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-07-17 22:25:42 +00:00
|
|
|
// Shutdown takes care of ensuring a clean shutdown of Messenger
|
|
|
|
func (m *Messenger) Shutdown() (err error) {
|
|
|
|
for _, task := range m.shutdownTasks {
|
|
|
|
if tErr := task(); tErr != nil {
|
|
|
|
if err == nil {
|
|
|
|
// First error appeared.
|
|
|
|
err = tErr
|
|
|
|
} else {
|
|
|
|
// We return all errors. They will be concatenated in the order of occurrence,
|
|
|
|
// however, they will also be returned as a single error.
|
|
|
|
err = errors.Wrap(err, tErr.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-08-27 12:38:59 +00:00
|
|
|
close(m.quit)
|
2019-07-17 22:25:42 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Messenger) EnableInstallation(id string) error {
|
2020-01-10 18:59:01 +00:00
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
|
|
|
|
installation, ok := m.allInstallations[id]
|
|
|
|
if !ok {
|
|
|
|
return errors.New("no installation found")
|
|
|
|
}
|
|
|
|
|
|
|
|
err := m.encryptor.EnableInstallation(&m.identity.PublicKey, id)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
installation.Enabled = true
|
|
|
|
m.allInstallations[id] = installation
|
|
|
|
return nil
|
2019-07-17 22:25:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Messenger) DisableInstallation(id string) error {
|
2020-01-10 18:59:01 +00:00
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
|
|
|
|
installation, ok := m.allInstallations[id]
|
|
|
|
if !ok {
|
|
|
|
return errors.New("no installation found")
|
|
|
|
}
|
|
|
|
|
|
|
|
err := m.encryptor.DisableInstallation(&m.identity.PublicKey, id)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
installation.Enabled = false
|
|
|
|
m.allInstallations[id] = installation
|
|
|
|
return nil
|
2019-07-17 22:25:42 +00:00
|
|
|
}
|
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
func (m *Messenger) Installations() []*multidevice.Installation {
|
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
installations := make([]*multidevice.Installation, len(m.allInstallations))
|
|
|
|
|
|
|
|
var i = 0
|
|
|
|
for _, installation := range m.allInstallations {
|
|
|
|
installations[i] = installation
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
return installations
|
2019-07-17 22:25:42 +00:00
|
|
|
}
|
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
func (m *Messenger) setInstallationMetadata(id string, data *multidevice.InstallationMetadata) error {
|
|
|
|
installation, ok := m.allInstallations[id]
|
|
|
|
if !ok {
|
|
|
|
return errors.New("no installation found")
|
|
|
|
}
|
|
|
|
|
|
|
|
installation.InstallationMetadata = data
|
2019-07-17 22:25:42 +00:00
|
|
|
return m.encryptor.SetInstallationMetadata(&m.identity.PublicKey, id, data)
|
|
|
|
}
|
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
func (m *Messenger) SetInstallationMetadata(id string, data *multidevice.InstallationMetadata) error {
|
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
return m.setInstallationMetadata(id, data)
|
|
|
|
}
|
|
|
|
|
2019-07-17 22:25:42 +00:00
|
|
|
// NOT IMPLEMENTED
|
|
|
|
func (m *Messenger) SelectMailserver(id string) error {
|
|
|
|
return ErrNotImplemented
|
|
|
|
}
|
|
|
|
|
|
|
|
// NOT IMPLEMENTED
|
|
|
|
func (m *Messenger) AddMailserver(enode string) error {
|
|
|
|
return ErrNotImplemented
|
|
|
|
}
|
|
|
|
|
|
|
|
// NOT IMPLEMENTED
|
|
|
|
func (m *Messenger) RemoveMailserver(id string) error {
|
|
|
|
return ErrNotImplemented
|
|
|
|
}
|
|
|
|
|
|
|
|
// NOT IMPLEMENTED
|
|
|
|
func (m *Messenger) Mailservers() ([]string, error) {
|
|
|
|
return nil, ErrNotImplemented
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Messenger) Join(chat Chat) error {
|
2019-10-14 14:10:48 +00:00
|
|
|
switch chat.ChatType {
|
|
|
|
case ChatTypeOneToOne:
|
2019-12-02 15:34:05 +00:00
|
|
|
pk, err := chat.PublicKey()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return m.transport.JoinPrivate(pk)
|
2019-10-14 14:10:48 +00:00
|
|
|
case ChatTypePrivateGroupChat:
|
|
|
|
members, err := chat.MembersAsPublicKeys()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return m.transport.JoinGroup(members)
|
2020-11-03 10:16:05 +00:00
|
|
|
case ChatTypePublic, ChatTypeProfile, ChatTypeTimeline:
|
2020-01-15 07:25:09 +00:00
|
|
|
return m.transport.JoinPublic(chat.ID)
|
2019-10-14 14:10:48 +00:00
|
|
|
default:
|
|
|
|
return errors.New("chat is neither public nor private")
|
2019-07-17 22:25:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-02 15:34:05 +00:00
|
|
|
// This is not accurate, it should not leave transport on removal of chat/group
|
|
|
|
// only once there is no more: Group chat with that member, one-to-one chat, contact added by us
|
2019-07-17 22:25:42 +00:00
|
|
|
func (m *Messenger) Leave(chat Chat) error {
|
2019-12-02 15:34:05 +00:00
|
|
|
switch chat.ChatType {
|
|
|
|
case ChatTypeOneToOne:
|
|
|
|
pk, err := chat.PublicKey()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return m.transport.LeavePrivate(pk)
|
|
|
|
case ChatTypePrivateGroupChat:
|
|
|
|
members, err := chat.MembersAsPublicKeys()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return m.transport.LeaveGroup(members)
|
2020-11-03 10:16:05 +00:00
|
|
|
case ChatTypePublic, ChatTypeProfile, ChatTypeTimeline:
|
2019-09-02 09:29:06 +00:00
|
|
|
return m.transport.LeavePublic(chat.Name)
|
2019-12-02 15:34:05 +00:00
|
|
|
default:
|
|
|
|
return errors.New("chat is neither public nor private")
|
2019-07-17 22:25:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-02 15:34:05 +00:00
|
|
|
func (m *Messenger) CreateGroupChatWithMembers(ctx context.Context, name string, members []string) (*MessengerResponse, error) {
|
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
|
|
|
|
var response MessengerResponse
|
|
|
|
logger := m.logger.With(zap.String("site", "CreateGroupChatWithMembers"))
|
|
|
|
logger.Info("Creating group chat", zap.String("name", name), zap.Any("members", members))
|
2020-02-07 11:30:26 +00:00
|
|
|
chat := CreateGroupChat(m.getTimesource())
|
2020-02-29 08:26:08 +00:00
|
|
|
|
|
|
|
clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
|
|
|
|
|
|
|
|
group, err := v1protocol.NewGroupWithCreator(name, clock, m.identity)
|
2019-10-14 14:10:48 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-02-29 08:26:08 +00:00
|
|
|
chat.LastClockValue = clock
|
2020-04-22 12:58:28 +00:00
|
|
|
|
|
|
|
chat.updateChatFromGroupMembershipChanges(contactIDFromPublicKey(&m.identity.PublicKey), group)
|
2019-12-02 15:34:05 +00:00
|
|
|
|
2020-02-29 08:26:08 +00:00
|
|
|
clock, _ = chat.NextClockAndTimestamp(m.getTimesource())
|
2020-10-30 11:26:08 +00:00
|
|
|
|
2019-12-02 15:34:05 +00:00
|
|
|
// Add members
|
2020-10-30 11:26:08 +00:00
|
|
|
if len(members) > 0 {
|
|
|
|
event := v1protocol.NewMembersAddedEvent(members, clock)
|
|
|
|
event.ChatID = chat.ID
|
|
|
|
err = event.Sign(m.identity)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-12-02 15:34:05 +00:00
|
|
|
|
2020-10-30 11:26:08 +00:00
|
|
|
err = group.ProcessEvent(event)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-12-02 15:34:05 +00:00
|
|
|
}
|
2020-10-30 11:26:08 +00:00
|
|
|
|
2019-12-02 15:34:05 +00:00
|
|
|
recipients, err := stringSliceToPublicKeys(group.Members(), true)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
encodedMessage, err := m.processor.EncodeMembershipUpdate(group, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
m.allChats[chat.ID] = &chat
|
|
|
|
|
2020-07-28 13:22:22 +00:00
|
|
|
_, err = m.dispatchMessage(ctx, common.RawMessage{
|
2020-01-10 18:59:01 +00:00
|
|
|
LocalChatID: chat.ID,
|
|
|
|
Payload: encodedMessage,
|
|
|
|
MessageType: protobuf.ApplicationMetadataMessage_MEMBERSHIP_UPDATE_MESSAGE,
|
|
|
|
Recipients: recipients,
|
|
|
|
ResendAutomatically: true,
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
2019-12-02 15:34:05 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2020-01-10 18:59:01 +00:00
|
|
|
|
2020-04-22 12:58:28 +00:00
|
|
|
chat.updateChatFromGroupMembershipChanges(contactIDFromPublicKey(&m.identity.PublicKey), group)
|
2019-12-02 15:34:05 +00:00
|
|
|
|
|
|
|
response.Chats = []*Chat{&chat}
|
|
|
|
response.Messages = buildSystemMessages(chat.MembershipUpdates, m.systemMessagesTranslations)
|
2020-05-20 12:16:12 +00:00
|
|
|
err = m.persistence.SaveMessages(response.Messages)
|
2020-01-17 12:39:09 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-12-02 15:34:05 +00:00
|
|
|
return &response, m.saveChat(&chat)
|
2019-10-14 14:10:48 +00:00
|
|
|
}
|
|
|
|
|
2020-08-07 13:49:37 +00:00
|
|
|
func (m *Messenger) CreateGroupChatFromInvitation(name string, chatID string, adminPK string) (*MessengerResponse, error) {
|
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
|
|
|
|
var response MessengerResponse
|
|
|
|
logger := m.logger.With(zap.String("site", "CreateGroupChatFromInvitation"))
|
|
|
|
logger.Info("Creating group chat from invitation", zap.String("name", name))
|
|
|
|
chat := CreateGroupChat(m.getTimesource())
|
|
|
|
chat.ID = chatID
|
|
|
|
chat.Name = name
|
|
|
|
chat.InvitationAdmin = adminPK
|
|
|
|
|
|
|
|
response.Chats = []*Chat{&chat}
|
|
|
|
|
|
|
|
return &response, m.saveChat(&chat)
|
|
|
|
}
|
|
|
|
|
2019-12-02 15:34:05 +00:00
|
|
|
func (m *Messenger) RemoveMemberFromGroupChat(ctx context.Context, chatID string, member string) (*MessengerResponse, error) {
|
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
|
|
|
|
var response MessengerResponse
|
|
|
|
logger := m.logger.With(zap.String("site", "RemoveMemberFromGroupChat"))
|
|
|
|
logger.Info("Removing member form group chat", zap.String("chatID", chatID), zap.String("member", member))
|
|
|
|
chat, ok := m.allChats[chatID]
|
|
|
|
if !ok {
|
2020-04-14 11:48:32 +00:00
|
|
|
return nil, ErrChatNotFound
|
2019-12-02 15:34:05 +00:00
|
|
|
}
|
|
|
|
|
2019-10-14 14:10:48 +00:00
|
|
|
group, err := newProtocolGroupFromChat(chat)
|
|
|
|
if err != nil {
|
2019-12-02 15:34:05 +00:00
|
|
|
return nil, err
|
2019-10-14 14:10:48 +00:00
|
|
|
}
|
2019-12-02 15:34:05 +00:00
|
|
|
|
|
|
|
// We save the initial recipients as we want to send updates to also
|
|
|
|
// the members kicked out
|
|
|
|
oldRecipients, err := stringSliceToPublicKeys(group.Members(), true)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2019-10-14 14:10:48 +00:00
|
|
|
}
|
2019-12-02 15:34:05 +00:00
|
|
|
|
2020-01-20 16:44:32 +00:00
|
|
|
clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
|
2019-12-02 15:34:05 +00:00
|
|
|
// Remove member
|
2020-01-15 07:25:09 +00:00
|
|
|
event := v1protocol.NewMemberRemovedEvent(member, clock)
|
2019-12-02 15:34:05 +00:00
|
|
|
event.ChatID = chat.ID
|
|
|
|
err = event.Sign(m.identity)
|
2019-10-14 14:10:48 +00:00
|
|
|
if err != nil {
|
2019-12-02 15:34:05 +00:00
|
|
|
return nil, err
|
2019-10-14 14:10:48 +00:00
|
|
|
}
|
2019-12-02 15:34:05 +00:00
|
|
|
|
|
|
|
err = group.ProcessEvent(event)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-01-10 18:59:01 +00:00
|
|
|
|
|
|
|
encodedMessage, err := m.processor.EncodeMembershipUpdate(group, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-07-28 13:22:22 +00:00
|
|
|
_, err = m.dispatchMessage(ctx, common.RawMessage{
|
2020-01-10 18:59:01 +00:00
|
|
|
LocalChatID: chat.ID,
|
|
|
|
Payload: encodedMessage,
|
|
|
|
MessageType: protobuf.ApplicationMetadataMessage_MEMBERSHIP_UPDATE_MESSAGE,
|
|
|
|
Recipients: oldRecipients,
|
|
|
|
ResendAutomatically: true,
|
|
|
|
})
|
|
|
|
if err != nil {
|
2019-12-02 15:34:05 +00:00
|
|
|
return nil, err
|
2019-10-14 14:10:48 +00:00
|
|
|
}
|
2020-01-10 18:59:01 +00:00
|
|
|
|
2020-04-22 12:58:28 +00:00
|
|
|
chat.updateChatFromGroupMembershipChanges(contactIDFromPublicKey(&m.identity.PublicKey), group)
|
|
|
|
|
2019-12-02 15:34:05 +00:00
|
|
|
response.Chats = []*Chat{chat}
|
|
|
|
response.Messages = buildSystemMessages(chat.MembershipUpdates, m.systemMessagesTranslations)
|
2020-05-20 12:16:12 +00:00
|
|
|
err = m.persistence.SaveMessages(response.Messages)
|
2020-01-17 12:39:09 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-12-02 15:34:05 +00:00
|
|
|
return &response, m.saveChat(chat)
|
2019-10-14 14:10:48 +00:00
|
|
|
}
|
|
|
|
|
2019-12-02 15:34:05 +00:00
|
|
|
func (m *Messenger) AddMembersToGroupChat(ctx context.Context, chatID string, members []string) (*MessengerResponse, error) {
|
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
|
|
|
|
var response MessengerResponse
|
|
|
|
logger := m.logger.With(zap.String("site", "AddMembersFromGroupChat"))
|
|
|
|
logger.Info("Adding members form group chat", zap.String("chatID", chatID), zap.Any("members", members))
|
|
|
|
chat, ok := m.allChats[chatID]
|
|
|
|
if !ok {
|
2020-04-14 11:48:32 +00:00
|
|
|
return nil, ErrChatNotFound
|
2019-12-02 15:34:05 +00:00
|
|
|
}
|
|
|
|
|
2019-10-14 14:10:48 +00:00
|
|
|
group, err := newProtocolGroupFromChat(chat)
|
|
|
|
if err != nil {
|
2019-12-02 15:34:05 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-01-20 16:44:32 +00:00
|
|
|
clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
|
2019-12-02 15:34:05 +00:00
|
|
|
// Add members
|
2020-01-15 07:25:09 +00:00
|
|
|
event := v1protocol.NewMembersAddedEvent(members, clock)
|
2019-12-02 15:34:05 +00:00
|
|
|
event.ChatID = chat.ID
|
|
|
|
err = event.Sign(m.identity)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-08-07 13:49:37 +00:00
|
|
|
//approve invitations
|
|
|
|
for _, member := range members {
|
|
|
|
logger.Info("ApproveInvitationByChatIdAndFrom", zap.String("chatID", chatID), zap.Any("member", member))
|
|
|
|
|
|
|
|
groupChatInvitation := &GroupChatInvitation{
|
|
|
|
GroupChatInvitation: protobuf.GroupChatInvitation{
|
|
|
|
ChatId: chat.ID,
|
|
|
|
},
|
|
|
|
From: member,
|
|
|
|
}
|
|
|
|
|
|
|
|
groupChatInvitation, err = m.persistence.InvitationByID(groupChatInvitation.ID())
|
2020-10-08 10:46:03 +00:00
|
|
|
if err != nil && err != common.ErrRecordNotFound {
|
2020-08-07 13:49:37 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if groupChatInvitation != nil {
|
|
|
|
groupChatInvitation.State = protobuf.GroupChatInvitation_APPROVED
|
|
|
|
|
|
|
|
err := m.persistence.SaveInvitation(groupChatInvitation)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
response.Invitations = append(response.Invitations, groupChatInvitation)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-02 15:34:05 +00:00
|
|
|
err = group.ProcessEvent(event)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
recipients, err := stringSliceToPublicKeys(group.Members(), true)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
encodedMessage, err := m.processor.EncodeMembershipUpdate(group, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-07-28 13:22:22 +00:00
|
|
|
_, err = m.dispatchMessage(ctx, common.RawMessage{
|
2020-01-10 18:59:01 +00:00
|
|
|
LocalChatID: chat.ID,
|
|
|
|
Payload: encodedMessage,
|
|
|
|
MessageType: protobuf.ApplicationMetadataMessage_MEMBERSHIP_UPDATE_MESSAGE,
|
|
|
|
Recipients: recipients,
|
|
|
|
ResendAutomatically: true,
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
2019-12-02 15:34:05 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2020-01-10 18:59:01 +00:00
|
|
|
|
2020-04-22 12:58:28 +00:00
|
|
|
chat.updateChatFromGroupMembershipChanges(contactIDFromPublicKey(&m.identity.PublicKey), group)
|
2019-12-02 15:34:05 +00:00
|
|
|
|
|
|
|
response.Chats = []*Chat{chat}
|
|
|
|
response.Messages = buildSystemMessages([]v1protocol.MembershipUpdateEvent{event}, m.systemMessagesTranslations)
|
2020-05-20 12:16:12 +00:00
|
|
|
err = m.persistence.SaveMessages(response.Messages)
|
2020-01-17 12:39:09 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-12-02 15:34:05 +00:00
|
|
|
|
|
|
|
return &response, m.saveChat(chat)
|
|
|
|
}
|
|
|
|
|
2020-04-14 11:48:32 +00:00
|
|
|
func (m *Messenger) ChangeGroupChatName(ctx context.Context, chatID string, name string) (*MessengerResponse, error) {
|
|
|
|
logger := m.logger.With(zap.String("site", "ChangeGroupChatName"))
|
|
|
|
logger.Info("Changing group chat name", zap.String("chatID", chatID), zap.String("name", name))
|
|
|
|
|
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
|
|
|
|
chat, ok := m.allChats[chatID]
|
|
|
|
if !ok {
|
|
|
|
return nil, ErrChatNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
group, err := newProtocolGroupFromChat(chat)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
|
|
|
|
// Add members
|
|
|
|
event := v1protocol.NewNameChangedEvent(name, clock)
|
|
|
|
event.ChatID = chat.ID
|
|
|
|
err = event.Sign(m.identity)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update in-memory group
|
|
|
|
err = group.ProcessEvent(event)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
recipients, err := stringSliceToPublicKeys(group.Members(), true)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
encodedMessage, err := m.processor.EncodeMembershipUpdate(group, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-07-28 13:22:22 +00:00
|
|
|
_, err = m.dispatchMessage(ctx, common.RawMessage{
|
2020-04-14 11:48:32 +00:00
|
|
|
LocalChatID: chat.ID,
|
|
|
|
Payload: encodedMessage,
|
|
|
|
MessageType: protobuf.ApplicationMetadataMessage_MEMBERSHIP_UPDATE_MESSAGE,
|
|
|
|
Recipients: recipients,
|
|
|
|
ResendAutomatically: true,
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-04-22 12:58:28 +00:00
|
|
|
chat.updateChatFromGroupMembershipChanges(contactIDFromPublicKey(&m.identity.PublicKey), group)
|
2020-04-14 11:48:32 +00:00
|
|
|
|
|
|
|
var response MessengerResponse
|
|
|
|
response.Chats = []*Chat{chat}
|
|
|
|
response.Messages = buildSystemMessages([]v1protocol.MembershipUpdateEvent{event}, m.systemMessagesTranslations)
|
2020-05-20 12:16:12 +00:00
|
|
|
err = m.persistence.SaveMessages(response.Messages)
|
2020-04-14 11:48:32 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &response, m.saveChat(chat)
|
|
|
|
}
|
|
|
|
|
2020-08-07 13:49:37 +00:00
|
|
|
func (m *Messenger) SendGroupChatInvitationRequest(ctx context.Context, chatID string, adminPK string,
|
|
|
|
message string) (*MessengerResponse, error) {
|
|
|
|
logger := m.logger.With(zap.String("site", "SendGroupChatInvitationRequest"))
|
|
|
|
logger.Info("Sending group chat invitation request", zap.String("chatID", chatID),
|
|
|
|
zap.String("adminPK", adminPK), zap.String("message", message))
|
|
|
|
|
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
|
|
|
|
var response MessengerResponse
|
|
|
|
|
|
|
|
// Get chat and clock
|
|
|
|
chat, ok := m.allChats[chatID]
|
|
|
|
if !ok {
|
|
|
|
return nil, ErrChatNotFound
|
|
|
|
}
|
|
|
|
clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
|
|
|
|
|
|
|
|
invitationR := &GroupChatInvitation{
|
|
|
|
GroupChatInvitation: protobuf.GroupChatInvitation{
|
|
|
|
Clock: clock,
|
|
|
|
ChatId: chatID,
|
|
|
|
IntroductionMessage: message,
|
|
|
|
State: protobuf.GroupChatInvitation_REQUEST,
|
|
|
|
},
|
|
|
|
From: types.EncodeHex(crypto.FromECDSAPub(&m.identity.PublicKey)),
|
|
|
|
}
|
|
|
|
|
|
|
|
encodedMessage, err := proto.Marshal(invitationR.GetProtobuf())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
spec := common.RawMessage{
|
|
|
|
LocalChatID: adminPK,
|
|
|
|
Payload: encodedMessage,
|
|
|
|
MessageType: protobuf.ApplicationMetadataMessage_GROUP_CHAT_INVITATION,
|
|
|
|
ResendAutomatically: true,
|
|
|
|
}
|
|
|
|
|
|
|
|
pkey, err := hex.DecodeString(adminPK[2:])
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
// Safety check, make sure is well formed
|
|
|
|
adminpk, err := crypto.UnmarshalPubkey(pkey)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
id, err := m.processor.SendPrivate(ctx, adminpk, spec)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
spec.ID = types.EncodeHex(id)
|
|
|
|
spec.SendCount++
|
|
|
|
err = m.persistence.SaveRawMessage(&spec)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
response.Invitations = []*GroupChatInvitation{invitationR}
|
|
|
|
|
|
|
|
err = m.persistence.SaveInvitation(invitationR)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &response, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Messenger) GetGroupChatInvitations() ([]*GroupChatInvitation, error) {
|
|
|
|
return m.persistence.GetGroupChatInvitations()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Messenger) SendGroupChatInvitationRejection(ctx context.Context, invitationRequestID string) (*MessengerResponse, error) {
|
|
|
|
logger := m.logger.With(zap.String("site", "SendGroupChatInvitationRejection"))
|
|
|
|
logger.Info("Sending group chat invitation reject", zap.String("invitationRequestID", invitationRequestID))
|
|
|
|
|
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
|
|
|
|
invitationR, err := m.persistence.InvitationByID(invitationRequestID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
invitationR.State = protobuf.GroupChatInvitation_REJECTED
|
|
|
|
|
|
|
|
// Get chat and clock
|
|
|
|
chat, ok := m.allChats[invitationR.ChatId]
|
|
|
|
if !ok {
|
|
|
|
return nil, ErrChatNotFound
|
|
|
|
}
|
|
|
|
clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
|
|
|
|
|
|
|
|
invitationR.Clock = clock
|
|
|
|
|
|
|
|
encodedMessage, err := proto.Marshal(invitationR.GetProtobuf())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
spec := common.RawMessage{
|
|
|
|
LocalChatID: invitationR.From,
|
|
|
|
Payload: encodedMessage,
|
|
|
|
MessageType: protobuf.ApplicationMetadataMessage_GROUP_CHAT_INVITATION,
|
|
|
|
ResendAutomatically: true,
|
|
|
|
}
|
|
|
|
|
|
|
|
pkey, err := hex.DecodeString(invitationR.From[2:])
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
// Safety check, make sure is well formed
|
|
|
|
userpk, err := crypto.UnmarshalPubkey(pkey)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
id, err := m.processor.SendPrivate(ctx, userpk, spec)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
spec.ID = types.EncodeHex(id)
|
|
|
|
spec.SendCount++
|
|
|
|
err = m.persistence.SaveRawMessage(&spec)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var response MessengerResponse
|
|
|
|
|
|
|
|
response.Invitations = []*GroupChatInvitation{invitationR}
|
|
|
|
|
|
|
|
err = m.persistence.SaveInvitation(invitationR)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &response, nil
|
|
|
|
}
|
|
|
|
|
2019-12-02 15:34:05 +00:00
|
|
|
func (m *Messenger) AddAdminsToGroupChat(ctx context.Context, chatID string, members []string) (*MessengerResponse, error) {
|
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
|
|
|
|
var response MessengerResponse
|
|
|
|
logger := m.logger.With(zap.String("site", "AddAdminsToGroupChat"))
|
|
|
|
logger.Info("Add admins to group chat", zap.String("chatID", chatID), zap.Any("members", members))
|
|
|
|
|
|
|
|
chat, ok := m.allChats[chatID]
|
|
|
|
if !ok {
|
2020-04-14 11:48:32 +00:00
|
|
|
return nil, ErrChatNotFound
|
2019-12-02 15:34:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
group, err := newProtocolGroupFromChat(chat)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-01-20 16:44:32 +00:00
|
|
|
clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
|
2019-12-02 15:34:05 +00:00
|
|
|
// Add members
|
2020-01-15 07:25:09 +00:00
|
|
|
event := v1protocol.NewAdminsAddedEvent(members, clock)
|
2019-12-02 15:34:05 +00:00
|
|
|
event.ChatID = chat.ID
|
|
|
|
err = event.Sign(m.identity)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = group.ProcessEvent(event)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
recipients, err := stringSliceToPublicKeys(group.Members(), true)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
encodedMessage, err := m.processor.EncodeMembershipUpdate(group, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-07-28 13:22:22 +00:00
|
|
|
_, err = m.dispatchMessage(ctx, common.RawMessage{
|
2020-01-10 18:59:01 +00:00
|
|
|
LocalChatID: chat.ID,
|
|
|
|
Payload: encodedMessage,
|
|
|
|
MessageType: protobuf.ApplicationMetadataMessage_MEMBERSHIP_UPDATE_MESSAGE,
|
|
|
|
Recipients: recipients,
|
|
|
|
ResendAutomatically: true,
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
2019-12-02 15:34:05 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2020-01-10 18:59:01 +00:00
|
|
|
|
2020-04-22 12:58:28 +00:00
|
|
|
chat.updateChatFromGroupMembershipChanges(contactIDFromPublicKey(&m.identity.PublicKey), group)
|
2019-12-02 15:34:05 +00:00
|
|
|
|
|
|
|
response.Chats = []*Chat{chat}
|
|
|
|
response.Messages = buildSystemMessages([]v1protocol.MembershipUpdateEvent{event}, m.systemMessagesTranslations)
|
2020-05-20 12:16:12 +00:00
|
|
|
err = m.persistence.SaveMessages(response.Messages)
|
2020-01-17 12:39:09 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-12-02 15:34:05 +00:00
|
|
|
|
|
|
|
return &response, m.saveChat(chat)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Messenger) ConfirmJoiningGroup(ctx context.Context, chatID string) (*MessengerResponse, error) {
|
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
|
|
|
|
var response MessengerResponse
|
|
|
|
|
|
|
|
chat, ok := m.allChats[chatID]
|
|
|
|
if !ok {
|
2020-04-14 11:48:32 +00:00
|
|
|
return nil, ErrChatNotFound
|
2019-12-02 15:34:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
err := m.Join(*chat)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
group, err := newProtocolGroupFromChat(chat)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2019-10-14 14:10:48 +00:00
|
|
|
}
|
2020-01-20 16:44:32 +00:00
|
|
|
clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
|
2019-11-21 16:19:22 +00:00
|
|
|
event := v1protocol.NewMemberJoinedEvent(
|
2020-01-15 07:25:09 +00:00
|
|
|
clock,
|
2019-10-14 14:10:48 +00:00
|
|
|
)
|
2019-12-02 15:34:05 +00:00
|
|
|
event.ChatID = chat.ID
|
|
|
|
err = event.Sign(m.identity)
|
2019-10-14 14:10:48 +00:00
|
|
|
if err != nil {
|
2019-12-02 15:34:05 +00:00
|
|
|
return nil, err
|
2019-10-14 14:10:48 +00:00
|
|
|
}
|
2019-12-02 15:34:05 +00:00
|
|
|
|
|
|
|
err = group.ProcessEvent(event)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
recipients, err := stringSliceToPublicKeys(group.Members(), true)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
encodedMessage, err := m.processor.EncodeMembershipUpdate(group, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-07-28 13:22:22 +00:00
|
|
|
_, err = m.dispatchMessage(ctx, common.RawMessage{
|
2020-01-10 18:59:01 +00:00
|
|
|
LocalChatID: chat.ID,
|
|
|
|
Payload: encodedMessage,
|
|
|
|
MessageType: protobuf.ApplicationMetadataMessage_MEMBERSHIP_UPDATE_MESSAGE,
|
|
|
|
Recipients: recipients,
|
|
|
|
ResendAutomatically: true,
|
|
|
|
})
|
|
|
|
if err != nil {
|
2019-12-02 15:34:05 +00:00
|
|
|
return nil, err
|
2019-10-14 14:10:48 +00:00
|
|
|
}
|
2020-01-10 18:59:01 +00:00
|
|
|
|
2020-04-22 12:58:28 +00:00
|
|
|
chat.updateChatFromGroupMembershipChanges(contactIDFromPublicKey(&m.identity.PublicKey), group)
|
2019-12-02 15:34:05 +00:00
|
|
|
|
|
|
|
response.Chats = []*Chat{chat}
|
|
|
|
response.Messages = buildSystemMessages([]v1protocol.MembershipUpdateEvent{event}, m.systemMessagesTranslations)
|
2020-05-20 12:16:12 +00:00
|
|
|
err = m.persistence.SaveMessages(response.Messages)
|
2020-01-17 12:39:09 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-12-02 15:34:05 +00:00
|
|
|
|
|
|
|
return &response, m.saveChat(chat)
|
2019-10-14 14:10:48 +00:00
|
|
|
}
|
|
|
|
|
2020-04-14 11:49:03 +00:00
|
|
|
func (m *Messenger) LeaveGroupChat(ctx context.Context, chatID string, remove bool) (*MessengerResponse, error) {
|
2019-12-02 15:34:05 +00:00
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
|
|
|
|
var response MessengerResponse
|
|
|
|
|
|
|
|
chat, ok := m.allChats[chatID]
|
|
|
|
if !ok {
|
2020-04-14 11:48:32 +00:00
|
|
|
return nil, ErrChatNotFound
|
2019-10-14 14:10:48 +00:00
|
|
|
}
|
2019-12-02 15:34:05 +00:00
|
|
|
|
|
|
|
err := m.Leave(*chat)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2019-10-14 14:10:48 +00:00
|
|
|
}
|
2019-12-02 15:34:05 +00:00
|
|
|
|
|
|
|
group, err := newProtocolGroupFromChat(chat)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2019-10-14 14:10:48 +00:00
|
|
|
}
|
2020-01-20 16:44:32 +00:00
|
|
|
clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
|
2019-12-02 15:34:05 +00:00
|
|
|
event := v1protocol.NewMemberRemovedEvent(
|
2020-01-15 07:25:09 +00:00
|
|
|
contactIDFromPublicKey(&m.identity.PublicKey),
|
|
|
|
clock,
|
2019-12-02 15:34:05 +00:00
|
|
|
)
|
|
|
|
event.ChatID = chat.ID
|
|
|
|
err = event.Sign(m.identity)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = group.ProcessEvent(event)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-10-14 14:10:48 +00:00
|
|
|
recipients, err := stringSliceToPublicKeys(group.Members(), true)
|
|
|
|
if err != nil {
|
2019-12-02 15:34:05 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
encodedMessage, err := m.processor.EncodeMembershipUpdate(group, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-07-28 13:22:22 +00:00
|
|
|
_, err = m.dispatchMessage(ctx, common.RawMessage{
|
2020-01-10 18:59:01 +00:00
|
|
|
LocalChatID: chat.ID,
|
|
|
|
Payload: encodedMessage,
|
|
|
|
MessageType: protobuf.ApplicationMetadataMessage_MEMBERSHIP_UPDATE_MESSAGE,
|
|
|
|
Recipients: recipients,
|
|
|
|
ResendAutomatically: true,
|
|
|
|
})
|
|
|
|
if err != nil {
|
2019-12-02 15:34:05 +00:00
|
|
|
return nil, err
|
2019-10-14 14:10:48 +00:00
|
|
|
}
|
2020-01-10 18:59:01 +00:00
|
|
|
|
2020-04-22 12:58:28 +00:00
|
|
|
chat.updateChatFromGroupMembershipChanges(contactIDFromPublicKey(&m.identity.PublicKey), group)
|
|
|
|
|
2020-04-14 11:49:03 +00:00
|
|
|
if remove {
|
|
|
|
chat.Active = false
|
|
|
|
}
|
2019-12-02 15:34:05 +00:00
|
|
|
|
|
|
|
response.Chats = []*Chat{chat}
|
|
|
|
response.Messages = buildSystemMessages([]v1protocol.MembershipUpdateEvent{event}, m.systemMessagesTranslations)
|
2020-05-20 12:16:12 +00:00
|
|
|
err = m.persistence.SaveMessages(response.Messages)
|
2020-01-17 12:39:09 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-12-02 15:34:05 +00:00
|
|
|
return &response, m.saveChat(chat)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Messenger) saveChat(chat *Chat) error {
|
2020-09-03 07:19:46 +00:00
|
|
|
previousChat, ok := m.allChats[chat.ID]
|
2020-05-20 12:16:12 +00:00
|
|
|
if chat.OneToOne() {
|
|
|
|
name, identicon, err := generateAliasAndIdenticon(chat.ID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
chat.Alias = name
|
|
|
|
chat.Identicon = identicon
|
|
|
|
}
|
2020-01-15 07:25:09 +00:00
|
|
|
// Sync chat if it's a new active public chat
|
|
|
|
if !ok && chat.Active && chat.Public() {
|
2020-09-03 07:19:46 +00:00
|
|
|
|
2020-01-15 07:25:09 +00:00
|
|
|
if err := m.syncPublicChat(context.Background(), chat); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-03 07:19:46 +00:00
|
|
|
// We check if it's a new chat, or chat.Active has changed
|
2020-09-16 13:19:47 +00:00
|
|
|
// we check here, but we only re-register once the chat has been
|
|
|
|
// saved an added
|
|
|
|
shouldRegisterForPushNotifications := chat.Public() && (!ok && chat.Active) || (ok && chat.Active != previousChat.Active)
|
2020-09-03 07:19:46 +00:00
|
|
|
|
2019-12-02 15:34:05 +00:00
|
|
|
err := m.persistence.SaveChat(*chat)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
m.allChats[chat.ID] = chat
|
2019-07-30 18:39:16 +00:00
|
|
|
|
2020-09-16 13:19:47 +00:00
|
|
|
if shouldRegisterForPushNotifications {
|
|
|
|
// Re-register for push notifications, as we want to receive mentions
|
|
|
|
if err := m.reregisterForPushNotifications(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2019-12-02 15:34:05 +00:00
|
|
|
return nil
|
2019-07-30 18:39:16 +00:00
|
|
|
}
|
|
|
|
|
2019-12-02 15:34:05 +00:00
|
|
|
func (m *Messenger) saveChats(chats []*Chat) error {
|
|
|
|
err := m.persistence.SaveChats(chats)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for _, chat := range chats {
|
|
|
|
m.allChats[chat.ID] = chat
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
2019-07-30 18:39:16 +00:00
|
|
|
}
|
|
|
|
|
2019-12-02 15:34:05 +00:00
|
|
|
func (m *Messenger) SaveChat(chat *Chat) error {
|
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
2020-01-15 07:25:09 +00:00
|
|
|
return m.saveChat(chat)
|
2019-12-02 15:34:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Messenger) Chats() []*Chat {
|
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
|
|
|
|
var chats []*Chat
|
|
|
|
|
|
|
|
for _, c := range m.allChats {
|
|
|
|
chats = append(chats, c)
|
2019-09-26 07:01:17 +00:00
|
|
|
}
|
2019-12-02 15:34:05 +00:00
|
|
|
|
|
|
|
return chats
|
2019-09-26 07:01:17 +00:00
|
|
|
}
|
|
|
|
|
2019-12-02 15:34:05 +00:00
|
|
|
func (m *Messenger) DeleteChat(chatID string) error {
|
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
|
|
|
|
err := m.persistence.DeleteChat(chatID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-09-03 07:19:46 +00:00
|
|
|
chat, ok := m.allChats[chatID]
|
|
|
|
|
|
|
|
if ok && chat.Active && chat.Public() {
|
|
|
|
delete(m.allChats, chatID)
|
|
|
|
return m.reregisterForPushNotifications()
|
|
|
|
}
|
2019-12-02 15:34:05 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
func (m *Messenger) isNewContact(contact *Contact) bool {
|
|
|
|
previousContact, ok := m.allContacts[contact.ID]
|
|
|
|
return contact.IsAdded() && (!ok || !previousContact.IsAdded())
|
|
|
|
}
|
2019-12-02 15:34:05 +00:00
|
|
|
|
2020-08-20 14:06:38 +00:00
|
|
|
func (m *Messenger) hasNicknameChanged(contact *Contact) bool {
|
|
|
|
previousContact, ok := m.allContacts[contact.ID]
|
|
|
|
if !ok {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return contact.LocalNickname != previousContact.LocalNickname
|
|
|
|
}
|
|
|
|
|
2020-07-20 13:58:54 +00:00
|
|
|
func (m *Messenger) removedContact(contact *Contact) bool {
|
|
|
|
previousContact, ok := m.allContacts[contact.ID]
|
|
|
|
if !ok {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return previousContact.IsAdded() && !contact.IsAdded()
|
|
|
|
}
|
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
func (m *Messenger) saveContact(contact *Contact) error {
|
2020-05-20 12:16:12 +00:00
|
|
|
name, identicon, err := generateAliasAndIdenticon(contact.ID)
|
2019-09-26 07:01:17 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
contact.Identicon = identicon
|
|
|
|
contact.Alias = name
|
|
|
|
|
2020-08-20 14:06:38 +00:00
|
|
|
if m.isNewContact(contact) || m.hasNicknameChanged(contact) {
|
2020-01-10 18:59:01 +00:00
|
|
|
err := m.syncContact(context.Background(), contact)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-22 07:41:40 +00:00
|
|
|
// We check if it should re-register with the push notification server
|
2020-08-18 15:07:48 +00:00
|
|
|
shouldReregisterForPushNotifications := (m.isNewContact(contact) || m.removedContact(contact))
|
2020-07-20 13:58:54 +00:00
|
|
|
|
2019-12-02 15:34:05 +00:00
|
|
|
err = m.persistence.SaveContact(contact, nil)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
m.allContacts[contact.ID] = contact
|
2020-01-10 18:59:01 +00:00
|
|
|
|
2020-07-20 13:58:54 +00:00
|
|
|
// Reregister only when data has changed
|
|
|
|
if shouldReregisterForPushNotifications {
|
2020-08-18 15:07:48 +00:00
|
|
|
return m.reregisterForPushNotifications()
|
2020-07-20 13:58:54 +00:00
|
|
|
}
|
|
|
|
|
2020-05-20 12:16:12 +00:00
|
|
|
return nil
|
2020-01-10 18:59:01 +00:00
|
|
|
}
|
2020-05-20 12:16:12 +00:00
|
|
|
|
2020-08-18 15:07:48 +00:00
|
|
|
func (m *Messenger) reregisterForPushNotifications() error {
|
|
|
|
m.logger.Info("contact state changed, re-registering for push notification")
|
|
|
|
if m.pushNotificationClient == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-09-02 14:11:16 +00:00
|
|
|
return m.pushNotificationClient.Reregister(m.pushNotificationOptions())
|
2020-08-18 15:07:48 +00:00
|
|
|
}
|
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
func (m *Messenger) SaveContact(contact *Contact) error {
|
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
return m.saveContact(contact)
|
2019-08-20 11:20:25 +00:00
|
|
|
}
|
|
|
|
|
2019-12-02 15:34:05 +00:00
|
|
|
func (m *Messenger) BlockContact(contact *Contact) ([]*Chat, error) {
|
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
chats, err := m.persistence.BlockContact(contact)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-08-26 08:36:58 +00:00
|
|
|
|
2019-12-02 15:34:05 +00:00
|
|
|
m.allContacts[contact.ID] = contact
|
|
|
|
for _, chat := range chats {
|
|
|
|
m.allChats[chat.ID] = chat
|
|
|
|
}
|
|
|
|
delete(m.allChats, contact.ID)
|
2020-08-26 08:36:58 +00:00
|
|
|
|
|
|
|
// re-register for push notifications
|
|
|
|
err = m.reregisterForPushNotifications()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-12-02 15:34:05 +00:00
|
|
|
return chats, nil
|
2019-07-30 18:39:16 +00:00
|
|
|
}
|
|
|
|
|
2019-12-02 15:34:05 +00:00
|
|
|
func (m *Messenger) Contacts() []*Contact {
|
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
var contacts []*Contact
|
|
|
|
for _, contact := range m.allContacts {
|
2020-04-17 11:22:38 +00:00
|
|
|
if contact.HasCustomFields() {
|
|
|
|
contacts = append(contacts, contact)
|
|
|
|
}
|
2019-12-02 15:34:05 +00:00
|
|
|
}
|
|
|
|
return contacts
|
2019-07-30 18:39:16 +00:00
|
|
|
}
|
|
|
|
|
2020-02-13 11:20:45 +00:00
|
|
|
// GetContactByID assumes pubKey includes 0x prefix
|
2020-06-04 13:32:47 +00:00
|
|
|
func (m *Messenger) GetContactByID(pubKey string) *Contact {
|
2020-02-13 11:20:45 +00:00
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
2020-06-04 13:32:47 +00:00
|
|
|
|
|
|
|
return m.allContacts[pubKey]
|
2020-02-13 11:20:45 +00:00
|
|
|
}
|
|
|
|
|
2020-12-15 14:43:41 +00:00
|
|
|
// pull a message from the database and send it again
|
|
|
|
func (m *Messenger) reSendRawMessage(ctx context.Context, messageID string) error {
|
2020-01-10 18:59:01 +00:00
|
|
|
message, err := m.persistence.RawMessageByID(messageID)
|
2019-09-26 07:01:17 +00:00
|
|
|
if err != nil {
|
2020-01-10 18:59:01 +00:00
|
|
|
return err
|
2019-07-17 22:25:42 +00:00
|
|
|
}
|
2020-01-10 18:59:01 +00:00
|
|
|
|
|
|
|
chat, ok := m.allChats[message.LocalChatID]
|
|
|
|
if !ok {
|
|
|
|
return errors.New("chat not found")
|
|
|
|
}
|
|
|
|
|
2020-07-28 13:22:22 +00:00
|
|
|
_, err = m.dispatchMessage(ctx, common.RawMessage{
|
2020-07-15 06:31:39 +00:00
|
|
|
LocalChatID: chat.ID,
|
|
|
|
Payload: message.Payload,
|
|
|
|
MessageType: message.MessageType,
|
|
|
|
Recipients: message.Recipients,
|
|
|
|
ResendAutomatically: message.ResendAutomatically,
|
2020-12-15 14:43:41 +00:00
|
|
|
SendCount: message.SendCount,
|
2020-01-10 18:59:01 +00:00
|
|
|
})
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-12-15 14:43:41 +00:00
|
|
|
// ReSendChatMessage pulls a message from the database and sends it again
|
|
|
|
func (m *Messenger) ReSendChatMessage(ctx context.Context, messageID string) error {
|
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
|
|
|
|
return m.reSendRawMessage(ctx, messageID)
|
|
|
|
}
|
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
func (m *Messenger) hasPairedDevices() bool {
|
|
|
|
var count int
|
|
|
|
for _, i := range m.allInstallations {
|
|
|
|
if i.Enabled {
|
2020-02-10 11:22:37 +00:00
|
|
|
count++
|
2020-01-10 18:59:01 +00:00
|
|
|
}
|
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
|
|
|
}
|
2020-01-10 18:59:01 +00:00
|
|
|
return count > 1
|
|
|
|
}
|
|
|
|
|
|
|
|
// sendToPairedDevices will check if we have any paired devices and send to them if necessary
|
2020-07-28 13:22:22 +00:00
|
|
|
func (m *Messenger) sendToPairedDevices(ctx context.Context, spec common.RawMessage) error {
|
2020-01-10 18:59:01 +00:00
|
|
|
hasPairedDevices := m.hasPairedDevices()
|
|
|
|
// We send a message to any paired device
|
|
|
|
if hasPairedDevices {
|
2020-05-13 13:24:52 +00:00
|
|
|
_, err := m.processor.SendPrivate(ctx, &m.identity.PublicKey, spec)
|
2020-01-10 18:59:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
|
|
|
}
|
2020-01-10 18:59:01 +00:00
|
|
|
return nil
|
|
|
|
}
|
2019-07-17 22:25:42 +00:00
|
|
|
|
2020-07-28 13:22:22 +00:00
|
|
|
func (m *Messenger) dispatchPairInstallationMessage(ctx context.Context, spec common.RawMessage) ([]byte, error) {
|
2020-01-10 18:59:01 +00:00
|
|
|
var err error
|
|
|
|
var id []byte
|
|
|
|
|
2020-05-13 13:24:52 +00:00
|
|
|
id, err = m.processor.SendPairInstallation(ctx, &m.identity.PublicKey, spec)
|
2020-01-10 18:59:01 +00:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
spec.ID = types.EncodeHex(id)
|
2020-02-10 11:22:37 +00:00
|
|
|
spec.SendCount++
|
2020-07-28 13:22:22 +00:00
|
|
|
err = m.persistence.SaveRawMessage(&spec)
|
2020-01-10 18:59:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return id, nil
|
|
|
|
}
|
|
|
|
|
2020-07-28 13:22:22 +00:00
|
|
|
func (m *Messenger) dispatchMessage(ctx context.Context, spec common.RawMessage) ([]byte, error) {
|
2020-01-10 18:59:01 +00:00
|
|
|
var err error
|
|
|
|
var id []byte
|
|
|
|
logger := m.logger.With(zap.String("site", "dispatchMessage"), zap.String("chatID", spec.LocalChatID))
|
|
|
|
chat, ok := m.allChats[spec.LocalChatID]
|
2019-12-02 15:34:05 +00:00
|
|
|
if !ok {
|
2020-01-10 18:59:01 +00:00
|
|
|
return nil, errors.New("no chat found")
|
2019-07-17 22:25:42 +00:00
|
|
|
}
|
|
|
|
|
2019-10-14 14:10:48 +00:00
|
|
|
switch chat.ChatType {
|
|
|
|
case ChatTypeOneToOne:
|
2019-12-02 15:34:05 +00:00
|
|
|
publicKey, err := chat.PublicKey()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-07-06 08:54:22 +00:00
|
|
|
if !common.IsPubKeyEqual(publicKey, &m.identity.PublicKey) {
|
2020-05-13 13:24:52 +00:00
|
|
|
id, err = m.processor.SendPrivate(ctx, publicKey, spec)
|
2019-12-02 15:34:05 +00:00
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-10-14 14:10:48 +00:00
|
|
|
}
|
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
|
|
|
|
2020-05-13 13:24:52 +00:00
|
|
|
err = m.sendToPairedDevices(ctx, spec)
|
2019-12-02 15:34:05 +00:00
|
|
|
|
2019-10-14 14:10:48 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-01-10 18:59:01 +00:00
|
|
|
|
2020-10-20 15:10:28 +00:00
|
|
|
case ChatTypePublic, ChatTypeProfile:
|
2020-01-10 18:59:01 +00:00
|
|
|
logger.Debug("sending public message", zap.String("chatName", chat.Name))
|
2020-05-13 13:24:52 +00:00
|
|
|
id, err = m.processor.SendPublic(ctx, chat.ID, spec)
|
2019-10-14 14:10:48 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
case ChatTypePrivateGroupChat:
|
|
|
|
logger.Debug("sending group message", zap.String("chatName", chat.Name))
|
|
|
|
if spec.Recipients == nil {
|
2020-09-03 09:54:05 +00:00
|
|
|
// Chat messages are only dispatched to users who joined
|
|
|
|
if spec.MessageType == protobuf.ApplicationMetadataMessage_CHAT_MESSAGE {
|
|
|
|
spec.Recipients, err = chat.JoinedMembersAsPublicKeys()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
spec.Recipients, err = chat.MembersAsPublicKeys()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-10-14 14:10:48 +00:00
|
|
|
}
|
|
|
|
}
|
2020-01-10 18:59:01 +00:00
|
|
|
hasPairedDevices := m.hasPairedDevices()
|
|
|
|
|
|
|
|
if !hasPairedDevices {
|
|
|
|
// Filter out my key from the recipients
|
|
|
|
n := 0
|
|
|
|
for _, recipient := range spec.Recipients {
|
2020-07-06 08:54:22 +00:00
|
|
|
if !common.IsPubKeyEqual(recipient, &m.identity.PublicKey) {
|
2020-01-10 18:59:01 +00:00
|
|
|
spec.Recipients[n] = recipient
|
|
|
|
n++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
spec.Recipients = spec.Recipients[:n]
|
2019-10-14 14:10:48 +00:00
|
|
|
}
|
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
|
|
|
|
2020-05-13 13:24:52 +00:00
|
|
|
spec.MessageType = protobuf.ApplicationMetadataMessage_MEMBERSHIP_UPDATE_MESSAGE
|
2020-01-10 18:59:01 +00:00
|
|
|
// We always wrap in group information
|
2020-05-13 13:24:52 +00:00
|
|
|
id, err = m.processor.SendGroup(ctx, spec.Recipients, spec)
|
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2019-07-17 22:25:42 +00:00
|
|
|
}
|
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
|
|
|
|
2019-10-14 14:10:48 +00:00
|
|
|
default:
|
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
|
|
|
return nil, errors.New("chat type not supported")
|
2019-10-14 14:10:48 +00:00
|
|
|
}
|
2020-01-10 18:59:01 +00:00
|
|
|
spec.ID = types.EncodeHex(id)
|
2020-02-10 11:22:37 +00:00
|
|
|
spec.SendCount++
|
2020-12-15 14:43:41 +00:00
|
|
|
spec.LastSent = m.getTimesource().GetCurrentTime()
|
2020-07-28 13:22:22 +00:00
|
|
|
err = m.persistence.SaveRawMessage(&spec)
|
2019-12-02 15:34:05 +00:00
|
|
|
if err != nil {
|
2020-01-10 18:59:01 +00:00
|
|
|
return nil, err
|
2019-12-02 15:34:05 +00:00
|
|
|
}
|
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
return id, nil
|
2019-07-17 22:25:42 +00:00
|
|
|
}
|
|
|
|
|
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
|
|
|
// SendChatMessage takes a minimal message and sends it based on the corresponding chat
|
2020-09-01 13:27:01 +00:00
|
|
|
func (m *Messenger) SendChatMessage(ctx context.Context, message *common.Message) (*MessengerResponse, error) {
|
2019-12-02 15:34:05 +00:00
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
2020-12-01 09:43:46 +00:00
|
|
|
return m.sendChatMessage(ctx, message)
|
|
|
|
}
|
|
|
|
|
|
|
|
// SendChatMessages takes a array of messages and sends it based on the corresponding chats
|
|
|
|
func (m *Messenger) SendChatMessages(ctx context.Context, messages []*common.Message) (*MessengerResponse, error) {
|
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
|
|
|
|
var response MessengerResponse
|
2019-12-02 15:34:05 +00:00
|
|
|
|
2020-12-01 09:43:46 +00:00
|
|
|
for _, message := range messages {
|
|
|
|
messageResponse, err := m.sendChatMessage(ctx, message)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
err = response.Merge(messageResponse)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return &response, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// SendChatMessage takes a minimal message and sends it based on the corresponding chat
|
|
|
|
func (m *Messenger) sendChatMessage(ctx context.Context, message *common.Message) (*MessengerResponse, error) {
|
2020-05-13 13:16:17 +00:00
|
|
|
if len(message.ImagePath) != 0 {
|
|
|
|
file, err := os.Open(message.ImagePath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
payload, err := ioutil.ReadAll(file)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
|
|
|
|
}
|
|
|
|
image := protobuf.ImageMessage{
|
|
|
|
Payload: payload,
|
|
|
|
Type: images.ImageType(payload),
|
|
|
|
}
|
|
|
|
message.Payload = &protobuf.ChatMessage_Image{Image: &image}
|
|
|
|
|
|
|
|
}
|
2020-06-17 18:55:49 +00:00
|
|
|
|
|
|
|
if len(message.AudioPath) != 0 {
|
|
|
|
file, err := os.Open(message.AudioPath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
payload, err := ioutil.ReadAll(file)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
|
|
|
|
}
|
2020-06-23 14:30:39 +00:00
|
|
|
audioMessage := message.GetAudio()
|
|
|
|
if audioMessage == nil {
|
|
|
|
return nil, errors.New("no audio has been passed")
|
2020-06-17 18:55:49 +00:00
|
|
|
}
|
2020-06-23 14:30:39 +00:00
|
|
|
audioMessage.Payload = payload
|
|
|
|
audioMessage.Type = audio.Type(payload)
|
|
|
|
message.Payload = &protobuf.ChatMessage_Audio{Audio: audioMessage}
|
2020-07-16 06:21:02 +00:00
|
|
|
err = os.Remove(message.AudioPath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-06-17 18:55:49 +00:00
|
|
|
}
|
|
|
|
|
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
|
|
|
var response MessengerResponse
|
|
|
|
|
|
|
|
// A valid added chat is required.
|
2019-12-02 15:34:05 +00:00
|
|
|
chat, ok := m.allChats[message.ChatId]
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("Chat not found")
|
2019-07-17 22:25:42 +00:00
|
|
|
}
|
|
|
|
|
2020-11-09 15:16:36 +00:00
|
|
|
err := m.handleStandaloneChatIdentity(chat)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = extendMessageFromChat(message, chat, &m.identity.PublicKey, m.getTimesource())
|
2019-07-26 07:17:29 +00:00
|
|
|
if err != nil {
|
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
|
|
|
return nil, err
|
2019-07-26 07:17:29 +00:00
|
|
|
}
|
|
|
|
|
2020-07-26 22:06:58 +00:00
|
|
|
encodedMessage, err := m.encodeChatEntity(chat, message)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2019-09-26 07:01:17 +00:00
|
|
|
}
|
|
|
|
|
2020-07-28 13:22:22 +00:00
|
|
|
id, err := m.dispatchMessage(ctx, common.RawMessage{
|
2020-07-22 07:41:40 +00:00
|
|
|
LocalChatID: chat.ID,
|
2020-09-02 14:11:16 +00:00
|
|
|
SendPushNotification: m.featureFlags.PushNotifications,
|
2020-07-22 07:41:40 +00:00
|
|
|
Payload: encodedMessage,
|
|
|
|
MessageType: protobuf.ApplicationMetadataMessage_CHAT_MESSAGE,
|
|
|
|
ResendAutomatically: true,
|
2020-01-10 18:59:01 +00:00
|
|
|
})
|
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2019-07-17 22:25:42 +00:00
|
|
|
}
|
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
message.ID = types.EncodeHex(id)
|
|
|
|
err = message.PrepareContent()
|
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2019-07-17 22:25:42 +00:00
|
|
|
}
|
|
|
|
|
2020-01-20 16:44:32 +00:00
|
|
|
err = chat.UpdateFromMessage(message, m.getTimesource())
|
2020-01-10 18:59:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-07-17 22:25:42 +00:00
|
|
|
|
2020-09-01 13:27:01 +00:00
|
|
|
err = m.persistence.SaveMessages([]*common.Message{message})
|
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-09-01 13:27:01 +00:00
|
|
|
response.Messages, err = m.pullMessagesAndResponsesFromDB([]*common.Message{message})
|
Add replies to messages
Currently replies to messages are handled in status-react.
This causes some issues with the fact that sometimes replies might come
out of order, they might be offloaded to the database etc.
This commit changes the behavior so that status-go always returns the
replies, and in case a reply comes out of order (first the reply, later
the message being replied to), it will include in the messages the
updated message.
It also adds some fields (RTL,Replace,LineCount) to the database which
were not previously saved, resulting in some potential bugs.
The method that we use to pull replies is currently a bit naive, we just
pull all the message again from the database, but has the advantage of
being simple. It will go through performance testing to make sure
performnace are acceptable, if so I think it's reasonable to avoid some
complexity.
2020-04-08 13:42:02 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
|
|
|
response.Chats = []*Chat{chat}
|
2019-12-02 15:34:05 +00:00
|
|
|
return &response, m.saveChat(chat)
|
2019-07-17 22:25:42 +00:00
|
|
|
}
|
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
// Send contact updates to all contacts added by us
|
|
|
|
func (m *Messenger) SendContactUpdates(ctx context.Context, ensName, profileImage string) error {
|
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
|
|
|
|
2020-01-15 07:25:09 +00:00
|
|
|
myID := contactIDFromPublicKey(&m.identity.PublicKey)
|
|
|
|
|
|
|
|
if _, err := m.sendContactUpdate(ctx, myID, ensName, profileImage); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: This should not be sending paired messages, as we do it above
|
2020-01-10 18:59:01 +00:00
|
|
|
for _, contact := range m.allContacts {
|
|
|
|
if contact.IsAdded() {
|
|
|
|
if _, err := m.sendContactUpdate(ctx, contact.ID, ensName, profileImage); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2019-09-02 09:29:06 +00:00
|
|
|
}
|
2020-01-10 18:59:01 +00:00
|
|
|
return nil
|
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
|
|
|
}
|
|
|
|
|
2020-08-26 08:36:58 +00:00
|
|
|
// NOTE: this endpoint does not add the contact, the reason being is that currently
|
|
|
|
// that's left as a responsibility to the client, which will call both `SendContactUpdate`
|
|
|
|
// and `SaveContact` with the correct system tag.
|
|
|
|
// Ideally we have a single endpoint that does both, but probably best to bring `ENS` name
|
|
|
|
// on the messenger first.
|
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
// SendContactUpdate sends a contact update to a user and adds the user to contacts
|
|
|
|
func (m *Messenger) SendContactUpdate(ctx context.Context, chatID, ensName, profileImage string) (*MessengerResponse, error) {
|
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
return m.sendContactUpdate(ctx, chatID, ensName, profileImage)
|
2019-12-02 15:34:05 +00:00
|
|
|
}
|
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
func (m *Messenger) sendContactUpdate(ctx context.Context, chatID, ensName, profileImage string) (*MessengerResponse, error) {
|
|
|
|
var response MessengerResponse
|
|
|
|
|
|
|
|
contact, ok := m.allContacts[chatID]
|
|
|
|
if !ok {
|
|
|
|
pubkeyBytes, err := types.DecodeHex(chatID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
publicKey, err := crypto.UnmarshalPubkey(pubkeyBytes)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
contact, err = buildContact(publicKey)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
chat, ok := m.allChats[chatID]
|
|
|
|
if !ok {
|
|
|
|
publicKey, err := contact.PublicKey()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-01-20 16:44:32 +00:00
|
|
|
chat = OneToOneFromPublicKey(publicKey, m.getTimesource())
|
2020-01-10 18:59:01 +00:00
|
|
|
// We don't want to show the chat to the user
|
|
|
|
chat.Active = false
|
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
|
|
|
}
|
2020-01-10 18:59:01 +00:00
|
|
|
|
|
|
|
m.allChats[chat.ID] = chat
|
2020-01-20 16:44:32 +00:00
|
|
|
clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
|
2020-01-10 18:59:01 +00:00
|
|
|
|
|
|
|
contactUpdate := &protobuf.ContactUpdate{
|
|
|
|
Clock: clock,
|
|
|
|
EnsName: ensName,
|
|
|
|
ProfileImage: profileImage}
|
|
|
|
encodedMessage, err := proto.Marshal(contactUpdate)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2019-12-02 15:34:05 +00:00
|
|
|
}
|
2020-01-10 18:59:01 +00:00
|
|
|
|
2020-07-28 13:22:22 +00:00
|
|
|
_, err = m.dispatchMessage(ctx, common.RawMessage{
|
2020-01-10 18:59:01 +00:00
|
|
|
LocalChatID: chatID,
|
|
|
|
Payload: encodedMessage,
|
|
|
|
MessageType: protobuf.ApplicationMetadataMessage_CONTACT_UPDATE,
|
|
|
|
ResendAutomatically: true,
|
|
|
|
})
|
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
response.Contacts = []*Contact{contact}
|
|
|
|
response.Chats = []*Chat{chat}
|
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
chat.LastClockValue = clock
|
|
|
|
err = m.saveChat(chat)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
|
|
|
}
|
2020-01-10 18:59:01 +00:00
|
|
|
return &response, m.saveContact(contact)
|
|
|
|
}
|
|
|
|
|
2020-01-15 07:25:09 +00:00
|
|
|
// SyncDevices sends all public chats and contacts to paired devices
|
|
|
|
func (m *Messenger) SyncDevices(ctx context.Context, ensName, photoPath string) error {
|
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
|
|
|
|
myID := contactIDFromPublicKey(&m.identity.PublicKey)
|
|
|
|
|
|
|
|
if _, err := m.sendContactUpdate(ctx, myID, ensName, photoPath); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, chat := range m.allChats {
|
|
|
|
if chat.Public() && chat.Active {
|
|
|
|
if err := m.syncPublicChat(ctx, chat); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, contact := range m.allContacts {
|
|
|
|
if contact.IsAdded() && contact.ID != myID {
|
|
|
|
if err := m.syncContact(ctx, contact); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
// SendPairInstallation sends a pair installation message
|
|
|
|
func (m *Messenger) SendPairInstallation(ctx context.Context) (*MessengerResponse, error) {
|
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
var err error
|
|
|
|
var response MessengerResponse
|
2019-12-02 15:34:05 +00:00
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
installation, ok := m.allInstallations[m.installationID]
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("no installation found")
|
2019-12-02 15:34:05 +00:00
|
|
|
}
|
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
if installation.InstallationMetadata == nil {
|
|
|
|
return nil, errors.New("no installation metadata")
|
|
|
|
}
|
|
|
|
|
2020-01-15 07:25:09 +00:00
|
|
|
chatID := contactIDFromPublicKey(&m.identity.PublicKey)
|
2020-01-10 18:59:01 +00:00
|
|
|
|
|
|
|
chat, ok := m.allChats[chatID]
|
|
|
|
if !ok {
|
2020-01-20 16:44:32 +00:00
|
|
|
chat = OneToOneFromPublicKey(&m.identity.PublicKey, m.getTimesource())
|
2020-01-10 18:59:01 +00:00
|
|
|
// We don't want to show the chat to the user
|
|
|
|
chat.Active = false
|
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
|
|
|
}
|
|
|
|
|
2019-12-02 15:34:05 +00:00
|
|
|
m.allChats[chat.ID] = chat
|
2020-01-20 16:44:32 +00:00
|
|
|
clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
|
2020-01-10 18:59:01 +00:00
|
|
|
|
|
|
|
pairMessage := &protobuf.PairInstallation{
|
|
|
|
Clock: clock,
|
|
|
|
Name: installation.InstallationMetadata.Name,
|
|
|
|
InstallationId: installation.ID,
|
|
|
|
DeviceType: installation.InstallationMetadata.DeviceType}
|
|
|
|
encodedMessage, err := proto.Marshal(pairMessage)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-07-28 13:22:22 +00:00
|
|
|
_, err = m.dispatchPairInstallationMessage(ctx, common.RawMessage{
|
2020-01-10 18:59:01 +00:00
|
|
|
LocalChatID: chatID,
|
|
|
|
Payload: encodedMessage,
|
|
|
|
MessageType: protobuf.ApplicationMetadataMessage_PAIR_INSTALLATION,
|
|
|
|
ResendAutomatically: true,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
response.Chats = []*Chat{chat}
|
2019-12-02 15:34:05 +00:00
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
chat.LastClockValue = clock
|
|
|
|
err = m.saveChat(chat)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &response, nil
|
2019-12-02 15:34:05 +00:00
|
|
|
}
|
|
|
|
|
2020-01-15 07:25:09 +00:00
|
|
|
// syncPublicChat sync a public chat with paired devices
|
|
|
|
func (m *Messenger) syncPublicChat(ctx context.Context, publicChat *Chat) error {
|
|
|
|
var err error
|
|
|
|
if !m.hasPairedDevices() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
chatID := contactIDFromPublicKey(&m.identity.PublicKey)
|
|
|
|
|
|
|
|
chat, ok := m.allChats[chatID]
|
|
|
|
if !ok {
|
2020-01-20 16:44:32 +00:00
|
|
|
chat = OneToOneFromPublicKey(&m.identity.PublicKey, m.getTimesource())
|
2020-01-15 07:25:09 +00:00
|
|
|
// We don't want to show the chat to the user
|
|
|
|
chat.Active = false
|
|
|
|
}
|
|
|
|
|
|
|
|
m.allChats[chat.ID] = chat
|
2020-01-20 16:44:32 +00:00
|
|
|
clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
|
2020-01-15 07:25:09 +00:00
|
|
|
|
|
|
|
syncMessage := &protobuf.SyncInstallationPublicChat{
|
|
|
|
Clock: clock,
|
|
|
|
Id: publicChat.ID,
|
|
|
|
}
|
|
|
|
encodedMessage, err := proto.Marshal(syncMessage)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-07-28 13:22:22 +00:00
|
|
|
_, err = m.dispatchMessage(ctx, common.RawMessage{
|
2020-01-15 07:25:09 +00:00
|
|
|
LocalChatID: chatID,
|
|
|
|
Payload: encodedMessage,
|
|
|
|
MessageType: protobuf.ApplicationMetadataMessage_SYNC_INSTALLATION_PUBLIC_CHAT,
|
|
|
|
ResendAutomatically: true,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
chat.LastClockValue = clock
|
|
|
|
return m.saveChat(chat)
|
|
|
|
}
|
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
// syncContact sync as contact with paired devices
|
|
|
|
func (m *Messenger) syncContact(ctx context.Context, contact *Contact) error {
|
|
|
|
var err error
|
|
|
|
if !m.hasPairedDevices() {
|
|
|
|
return nil
|
2019-12-02 15:34:05 +00:00
|
|
|
}
|
2020-01-15 07:25:09 +00:00
|
|
|
chatID := contactIDFromPublicKey(&m.identity.PublicKey)
|
2019-12-02 15:34:05 +00:00
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
chat, ok := m.allChats[chatID]
|
|
|
|
if !ok {
|
2020-01-20 16:44:32 +00:00
|
|
|
chat = OneToOneFromPublicKey(&m.identity.PublicKey, m.getTimesource())
|
2020-01-10 18:59:01 +00:00
|
|
|
// We don't want to show the chat to the user
|
|
|
|
chat.Active = false
|
|
|
|
}
|
2019-12-02 15:34:05 +00:00
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
m.allChats[chat.ID] = chat
|
2020-01-20 16:44:32 +00:00
|
|
|
clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
|
2020-01-10 18:59:01 +00:00
|
|
|
|
|
|
|
syncMessage := &protobuf.SyncInstallationContact{
|
2020-08-20 14:06:38 +00:00
|
|
|
Clock: clock,
|
|
|
|
Id: contact.ID,
|
|
|
|
EnsName: contact.Name,
|
|
|
|
ProfileImage: contact.Photo,
|
|
|
|
LocalNickname: contact.LocalNickname,
|
2020-01-10 18:59:01 +00:00
|
|
|
}
|
|
|
|
encodedMessage, err := proto.Marshal(syncMessage)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-07-28 13:22:22 +00:00
|
|
|
_, err = m.dispatchMessage(ctx, common.RawMessage{
|
2020-01-10 18:59:01 +00:00
|
|
|
LocalChatID: chatID,
|
|
|
|
Payload: encodedMessage,
|
|
|
|
MessageType: protobuf.ApplicationMetadataMessage_SYNC_INSTALLATION_CONTACT,
|
|
|
|
ResendAutomatically: true,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
chat.LastClockValue = clock
|
|
|
|
return m.saveChat(chat)
|
|
|
|
}
|
|
|
|
|
|
|
|
// RetrieveAll retrieves messages from all filters, processes them and returns a
|
|
|
|
// MessengerResponse to the client
|
|
|
|
func (m *Messenger) RetrieveAll() (*MessengerResponse, error) {
|
|
|
|
chatWithMessages, err := m.transport.RetrieveRawAll()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2019-12-02 15:34:05 +00:00
|
|
|
}
|
2020-01-10 18:59:01 +00:00
|
|
|
|
|
|
|
return m.handleRetrievedMessages(chatWithMessages)
|
|
|
|
}
|
|
|
|
|
|
|
|
type CurrentMessageState struct {
|
|
|
|
// Message is the protobuf message received
|
|
|
|
Message protobuf.ChatMessage
|
|
|
|
// MessageID is the ID of the message
|
|
|
|
MessageID string
|
|
|
|
// WhisperTimestamp is the whisper timestamp of the message
|
|
|
|
WhisperTimestamp uint64
|
|
|
|
// Contact is the contact associated with the author of the message
|
|
|
|
Contact *Contact
|
|
|
|
// PublicKey is the public key of the author of the message
|
|
|
|
PublicKey *ecdsa.PublicKey
|
|
|
|
}
|
|
|
|
|
|
|
|
type ReceivedMessageState struct {
|
|
|
|
// State on the message being processed
|
|
|
|
CurrentMessageState *CurrentMessageState
|
|
|
|
// AllChats in memory
|
|
|
|
AllChats map[string]*Chat
|
|
|
|
// List of chats modified
|
|
|
|
ModifiedChats map[string]bool
|
|
|
|
// All contacts in memory
|
|
|
|
AllContacts map[string]*Contact
|
2020-02-10 11:22:37 +00:00
|
|
|
// List of contacts modified
|
2020-01-10 18:59:01 +00:00
|
|
|
ModifiedContacts map[string]bool
|
|
|
|
// All installations in memory
|
|
|
|
AllInstallations map[string]*multidevice.Installation
|
|
|
|
// List of installations modified
|
|
|
|
ModifiedInstallations map[string]bool
|
|
|
|
// Map of existing messages
|
|
|
|
ExistingMessagesMap map[string]bool
|
2020-07-27 12:27:48 +00:00
|
|
|
// EmojiReactions is a list of emoji reactions for the current batch
|
|
|
|
// indexed by from-message-id-emoji-type
|
|
|
|
EmojiReactions map[string]*EmojiReaction
|
2020-08-07 13:49:37 +00:00
|
|
|
// GroupChatInvitations is a list of invitation requests or rejections
|
|
|
|
GroupChatInvitations map[string]*GroupChatInvitation
|
2020-01-10 18:59:01 +00:00
|
|
|
// Response to the client
|
|
|
|
Response *MessengerResponse
|
2020-02-07 11:30:26 +00:00
|
|
|
// Timesource is a time source for clock values/timestamps.
|
|
|
|
Timesource TimeSource
|
2019-12-02 15:34:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Messenger) handleRetrievedMessages(chatWithMessages map[transport.Filter][]*types.Message) (*MessengerResponse, error) {
|
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
2020-01-10 18:59:01 +00:00
|
|
|
messageState := &ReceivedMessageState{
|
|
|
|
AllChats: m.allChats,
|
|
|
|
ModifiedChats: make(map[string]bool),
|
|
|
|
AllContacts: m.allContacts,
|
|
|
|
ModifiedContacts: make(map[string]bool),
|
|
|
|
AllInstallations: m.allInstallations,
|
|
|
|
ModifiedInstallations: m.modifiedInstallations,
|
|
|
|
ExistingMessagesMap: make(map[string]bool),
|
2020-07-27 12:27:48 +00:00
|
|
|
EmojiReactions: make(map[string]*EmojiReaction),
|
2020-08-07 13:49:37 +00:00
|
|
|
GroupChatInvitations: make(map[string]*GroupChatInvitation),
|
2020-01-10 18:59:01 +00:00
|
|
|
Response: &MessengerResponse{},
|
2020-01-20 16:44:32 +00:00
|
|
|
Timesource: m.getTimesource(),
|
2019-12-02 15:34:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
logger := m.logger.With(zap.String("site", "RetrieveAll"))
|
2020-01-28 11:16:28 +00:00
|
|
|
for _, messages := range chatWithMessages {
|
2019-10-09 14:22:53 +00:00
|
|
|
for _, shhMessage := range messages {
|
2019-09-02 09:29:06 +00:00
|
|
|
// TODO: fix this to use an exported method.
|
2020-07-06 08:54:22 +00:00
|
|
|
statusMessages, err := m.processor.HandleMessages(shhMessage, true)
|
2019-09-02 09:29:06 +00:00
|
|
|
if err != nil {
|
|
|
|
logger.Info("failed to decode messages", zap.Error(err))
|
|
|
|
continue
|
|
|
|
}
|
2019-09-26 07:01:17 +00:00
|
|
|
|
2020-03-09 06:19:23 +00:00
|
|
|
logger.Debug("processing messages further", zap.Int("count", len(statusMessages)))
|
|
|
|
|
2019-11-15 08:52:28 +00:00
|
|
|
for _, msg := range statusMessages {
|
2019-12-02 15:34:05 +00:00
|
|
|
publicKey := msg.SigPubKey()
|
|
|
|
|
2020-07-31 12:22:05 +00:00
|
|
|
m.handleInstallations(msg.Installations)
|
|
|
|
err := m.handleSharedSecrets(msg.SharedSecrets)
|
|
|
|
if err != nil {
|
|
|
|
// log and continue, non-critical error
|
|
|
|
logger.Warn("failed to handle shared secrets")
|
|
|
|
}
|
|
|
|
|
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
|
|
|
// Check for messages from blocked users
|
2020-01-15 07:25:09 +00:00
|
|
|
senderID := contactIDFromPublicKey(publicKey)
|
2020-01-10 18:59:01 +00:00
|
|
|
if _, ok := messageState.AllContacts[senderID]; ok && messageState.AllContacts[senderID].IsBlocked() {
|
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
// Don't process duplicates
|
2019-12-02 15:34:05 +00:00
|
|
|
messageID := types.EncodeHex(msg.ID)
|
2020-01-10 18:59:01 +00:00
|
|
|
exists, err := m.handler.messageExists(messageID, messageState.ExistingMessagesMap)
|
2019-12-02 15:34:05 +00:00
|
|
|
if err != nil {
|
|
|
|
logger.Warn("failed to check message exists", zap.Error(err))
|
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
|
|
|
}
|
2019-12-02 15:34:05 +00:00
|
|
|
if exists {
|
2020-08-18 15:07:48 +00:00
|
|
|
logger.Debug("messageExists", zap.String("messageID", messageID))
|
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
var contact *Contact
|
2020-01-10 18:59:01 +00:00
|
|
|
if c, ok := messageState.AllContacts[senderID]; ok {
|
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
|
|
|
contact = c
|
|
|
|
} else {
|
|
|
|
c, err := buildContact(publicKey)
|
|
|
|
if err != nil {
|
|
|
|
logger.Info("failed to build contact", zap.Error(err))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
contact = c
|
2020-01-10 18:59:01 +00:00
|
|
|
messageState.AllContacts[senderID] = c
|
|
|
|
messageState.ModifiedContacts[contact.ID] = true
|
2019-12-02 15:34:05 +00:00
|
|
|
}
|
2020-01-10 18:59:01 +00:00
|
|
|
messageState.CurrentMessageState = &CurrentMessageState{
|
2019-12-02 15:34:05 +00:00
|
|
|
MessageID: messageID,
|
|
|
|
WhisperTimestamp: uint64(msg.TransportMessage.Timestamp) * 1000,
|
|
|
|
Contact: contact,
|
|
|
|
PublicKey: publicKey,
|
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
|
|
|
}
|
|
|
|
|
2019-11-15 08:52:28 +00:00
|
|
|
if msg.ParsedMessage != nil {
|
2020-01-10 18:59:01 +00:00
|
|
|
logger.Debug("Handling parsed message")
|
2020-07-27 10:13:22 +00:00
|
|
|
switch msg.ParsedMessage.Interface().(type) {
|
2019-12-02 15:34:05 +00:00
|
|
|
case protobuf.MembershipUpdateMessage:
|
2020-01-10 18:59:01 +00:00
|
|
|
logger.Debug("Handling MembershipUpdateMessage")
|
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
|
|
|
|
2020-07-27 10:13:22 +00:00
|
|
|
rawMembershipUpdate := msg.ParsedMessage.Interface().(protobuf.MembershipUpdateMessage)
|
2020-01-10 18:59:01 +00:00
|
|
|
|
|
|
|
err = m.handler.HandleMembershipUpdate(messageState, messageState.AllChats[rawMembershipUpdate.ChatId], rawMembershipUpdate, m.systemMessagesTranslations)
|
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
|
|
|
if err != nil {
|
2020-01-10 18:59:01 +00:00
|
|
|
logger.Warn("failed to handle MembershipUpdate", zap.Error(err))
|
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
case protobuf.ChatMessage:
|
|
|
|
logger.Debug("Handling ChatMessage")
|
2020-07-27 10:13:22 +00:00
|
|
|
messageState.CurrentMessageState.Message = msg.ParsedMessage.Interface().(protobuf.ChatMessage)
|
2020-01-10 18:59:01 +00:00
|
|
|
err = m.handler.HandleChatMessage(messageState)
|
|
|
|
if err != nil {
|
|
|
|
logger.Warn("failed to handle ChatMessage", zap.Error(err))
|
|
|
|
continue
|
|
|
|
}
|
2020-07-25 11:46:43 +00:00
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
case protobuf.PairInstallation:
|
2020-07-06 08:54:22 +00:00
|
|
|
if !common.IsPubKeyEqual(messageState.CurrentMessageState.PublicKey, &m.identity.PublicKey) {
|
2020-01-10 18:59:01 +00:00
|
|
|
logger.Warn("not coming from us, ignoring")
|
|
|
|
continue
|
|
|
|
}
|
2020-07-27 10:13:22 +00:00
|
|
|
p := msg.ParsedMessage.Interface().(protobuf.PairInstallation)
|
2020-01-10 18:59:01 +00:00
|
|
|
logger.Debug("Handling PairInstallation", zap.Any("message", p))
|
|
|
|
err = m.handler.HandlePairInstallation(messageState, p)
|
2019-12-02 15:34:05 +00:00
|
|
|
if err != nil {
|
2020-01-10 18:59:01 +00:00
|
|
|
logger.Warn("failed to handle PairInstallation", zap.Error(err))
|
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
case protobuf.SyncInstallationContact:
|
2020-07-06 08:54:22 +00:00
|
|
|
if !common.IsPubKeyEqual(messageState.CurrentMessageState.PublicKey, &m.identity.PublicKey) {
|
2020-01-10 18:59:01 +00:00
|
|
|
logger.Warn("not coming from us, ignoring")
|
|
|
|
continue
|
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
|
|
|
}
|
|
|
|
|
2020-07-27 10:13:22 +00:00
|
|
|
p := msg.ParsedMessage.Interface().(protobuf.SyncInstallationContact)
|
2020-01-10 18:59:01 +00:00
|
|
|
logger.Debug("Handling SyncInstallationContact", zap.Any("message", p))
|
|
|
|
err = m.handler.HandleSyncInstallationContact(messageState, p)
|
|
|
|
if err != nil {
|
|
|
|
logger.Warn("failed to handle SyncInstallationContact", zap.Error(err))
|
|
|
|
continue
|
|
|
|
}
|
2020-07-25 11:46:43 +00:00
|
|
|
|
2020-01-15 07:25:09 +00:00
|
|
|
case protobuf.SyncInstallationPublicChat:
|
2020-07-06 08:54:22 +00:00
|
|
|
if !common.IsPubKeyEqual(messageState.CurrentMessageState.PublicKey, &m.identity.PublicKey) {
|
2020-01-15 07:25:09 +00:00
|
|
|
logger.Warn("not coming from us, ignoring")
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-07-27 10:13:22 +00:00
|
|
|
p := msg.ParsedMessage.Interface().(protobuf.SyncInstallationPublicChat)
|
2020-01-15 07:25:09 +00:00
|
|
|
logger.Debug("Handling SyncInstallationPublicChat", zap.Any("message", p))
|
2020-09-07 08:25:57 +00:00
|
|
|
added := m.handler.HandleSyncInstallationPublicChat(messageState, p)
|
2020-07-25 11:46:43 +00:00
|
|
|
|
2020-09-03 07:19:46 +00:00
|
|
|
// We re-register as we want to receive mentions from the newly joined public chat
|
|
|
|
if added {
|
|
|
|
logger.Debug("newly synced public chat, re-registering for push notifications")
|
|
|
|
err := m.reregisterForPushNotifications()
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
logger.Warn("could not re-register for push notifications", zap.Error(err))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
case protobuf.RequestAddressForTransaction:
|
2020-07-27 10:13:22 +00:00
|
|
|
command := msg.ParsedMessage.Interface().(protobuf.RequestAddressForTransaction)
|
2020-01-10 18:59:01 +00:00
|
|
|
logger.Debug("Handling RequestAddressForTransaction", zap.Any("message", command))
|
|
|
|
err = m.handler.HandleRequestAddressForTransaction(messageState, command)
|
|
|
|
if err != nil {
|
|
|
|
logger.Warn("failed to handle RequestAddressForTransaction", zap.Error(err))
|
|
|
|
continue
|
|
|
|
}
|
2020-07-25 11:46:43 +00:00
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
case protobuf.SendTransaction:
|
2020-07-27 10:13:22 +00:00
|
|
|
command := msg.ParsedMessage.Interface().(protobuf.SendTransaction)
|
2020-01-10 18:59:01 +00:00
|
|
|
logger.Debug("Handling SendTransaction", zap.Any("message", command))
|
|
|
|
err = m.handler.HandleSendTransaction(messageState, command)
|
|
|
|
if err != nil {
|
|
|
|
logger.Warn("failed to handle SendTransaction", zap.Error(err))
|
|
|
|
continue
|
|
|
|
}
|
2020-07-25 11:46:43 +00:00
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
case protobuf.AcceptRequestAddressForTransaction:
|
2020-07-27 10:13:22 +00:00
|
|
|
command := msg.ParsedMessage.Interface().(protobuf.AcceptRequestAddressForTransaction)
|
2020-01-10 18:59:01 +00:00
|
|
|
logger.Debug("Handling AcceptRequestAddressForTransaction")
|
|
|
|
err = m.handler.HandleAcceptRequestAddressForTransaction(messageState, command)
|
|
|
|
if err != nil {
|
|
|
|
logger.Warn("failed to handle AcceptRequestAddressForTransaction", zap.Error(err))
|
|
|
|
continue
|
|
|
|
}
|
2019-12-02 15:34:05 +00:00
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
case protobuf.DeclineRequestAddressForTransaction:
|
2020-07-27 10:13:22 +00:00
|
|
|
command := msg.ParsedMessage.Interface().(protobuf.DeclineRequestAddressForTransaction)
|
2020-01-10 18:59:01 +00:00
|
|
|
logger.Debug("Handling DeclineRequestAddressForTransaction")
|
|
|
|
err = m.handler.HandleDeclineRequestAddressForTransaction(messageState, command)
|
|
|
|
if err != nil {
|
|
|
|
logger.Warn("failed to handle DeclineRequestAddressForTransaction", zap.Error(err))
|
|
|
|
continue
|
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
|
|
|
}
|
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
case protobuf.DeclineRequestTransaction:
|
2020-07-27 10:13:22 +00:00
|
|
|
command := msg.ParsedMessage.Interface().(protobuf.DeclineRequestTransaction)
|
2020-01-10 18:59:01 +00:00
|
|
|
logger.Debug("Handling DeclineRequestTransaction")
|
|
|
|
err = m.handler.HandleDeclineRequestTransaction(messageState, command)
|
2019-12-02 15:34:05 +00:00
|
|
|
if err != nil {
|
2020-01-10 18:59:01 +00:00
|
|
|
logger.Warn("failed to handle DeclineRequestTransaction", zap.Error(err))
|
2019-12-02 15:34:05 +00:00
|
|
|
continue
|
|
|
|
}
|
2020-01-10 18:59:01 +00:00
|
|
|
|
|
|
|
case protobuf.RequestTransaction:
|
2020-07-27 10:13:22 +00:00
|
|
|
command := msg.ParsedMessage.Interface().(protobuf.RequestTransaction)
|
2020-01-10 18:59:01 +00:00
|
|
|
logger.Debug("Handling RequestTransaction")
|
|
|
|
err = m.handler.HandleRequestTransaction(messageState, command)
|
|
|
|
if err != nil {
|
|
|
|
logger.Warn("failed to handle RequestTransaction", zap.Error(err))
|
|
|
|
continue
|
|
|
|
}
|
2020-07-25 11:46:43 +00:00
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
case protobuf.ContactUpdate:
|
|
|
|
logger.Debug("Handling ContactUpdate")
|
2020-07-27 10:13:22 +00:00
|
|
|
contactUpdate := msg.ParsedMessage.Interface().(protobuf.ContactUpdate)
|
2020-01-10 18:59:01 +00:00
|
|
|
err = m.handler.HandleContactUpdate(messageState, contactUpdate)
|
|
|
|
if err != nil {
|
|
|
|
logger.Warn("failed to handle ContactUpdate", zap.Error(err))
|
|
|
|
continue
|
2019-12-02 15:34:05 +00:00
|
|
|
}
|
2020-07-06 08:54:22 +00:00
|
|
|
case protobuf.PushNotificationQuery:
|
|
|
|
logger.Debug("Received PushNotificationQuery")
|
|
|
|
if m.pushNotificationServer == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
logger.Debug("Handling PushNotificationQuery")
|
2020-07-27 10:13:22 +00:00
|
|
|
if err := m.pushNotificationServer.HandlePushNotificationQuery(publicKey, msg.ID, msg.ParsedMessage.Interface().(protobuf.PushNotificationQuery)); err != nil {
|
2020-07-06 08:54:22 +00:00
|
|
|
logger.Warn("failed to handle PushNotificationQuery", zap.Error(err))
|
|
|
|
}
|
|
|
|
// We continue in any case, no changes to messenger
|
|
|
|
continue
|
2020-07-09 16:52:26 +00:00
|
|
|
case protobuf.PushNotificationRegistrationResponse:
|
|
|
|
logger.Debug("Received PushNotificationRegistrationResponse")
|
|
|
|
if m.pushNotificationClient == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
logger.Debug("Handling PushNotificationRegistrationResponse")
|
2020-07-27 10:13:22 +00:00
|
|
|
if err := m.pushNotificationClient.HandlePushNotificationRegistrationResponse(publicKey, msg.ParsedMessage.Interface().(protobuf.PushNotificationRegistrationResponse)); err != nil {
|
2020-07-09 16:52:26 +00:00
|
|
|
logger.Warn("failed to handle PushNotificationRegistrationResponse", zap.Error(err))
|
|
|
|
}
|
|
|
|
// We continue in any case, no changes to messenger
|
|
|
|
continue
|
2020-07-31 13:46:27 +00:00
|
|
|
case protobuf.ContactCodeAdvertisement:
|
|
|
|
logger.Debug("Received ContactCodeAdvertisement")
|
|
|
|
if m.pushNotificationClient == nil {
|
|
|
|
continue
|
|
|
|
}
|
2020-08-18 15:07:48 +00:00
|
|
|
logger.Debug("Handling ContactCodeAdvertisement")
|
2020-07-31 13:46:27 +00:00
|
|
|
if err := m.pushNotificationClient.HandleContactCodeAdvertisement(publicKey, msg.ParsedMessage.Interface().(protobuf.ContactCodeAdvertisement)); err != nil {
|
|
|
|
logger.Warn("failed to handle ContactCodeAdvertisement", zap.Error(err))
|
|
|
|
}
|
|
|
|
// We continue in any case, no changes to messenger
|
|
|
|
continue
|
|
|
|
|
2020-07-20 08:06:24 +00:00
|
|
|
case protobuf.PushNotificationResponse:
|
|
|
|
logger.Debug("Received PushNotificationResponse")
|
|
|
|
if m.pushNotificationClient == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
logger.Debug("Handling PushNotificationResponse")
|
2020-07-27 10:13:22 +00:00
|
|
|
if err := m.pushNotificationClient.HandlePushNotificationResponse(publicKey, msg.ParsedMessage.Interface().(protobuf.PushNotificationResponse)); err != nil {
|
2020-07-20 08:06:24 +00:00
|
|
|
logger.Warn("failed to handle PushNotificationResponse", zap.Error(err))
|
|
|
|
}
|
|
|
|
// We continue in any case, no changes to messenger
|
|
|
|
continue
|
2020-07-09 16:52:26 +00:00
|
|
|
|
|
|
|
case protobuf.PushNotificationQueryResponse:
|
|
|
|
logger.Debug("Received PushNotificationQueryResponse")
|
|
|
|
if m.pushNotificationClient == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
logger.Debug("Handling PushNotificationQueryResponse")
|
2020-07-27 10:13:22 +00:00
|
|
|
if err := m.pushNotificationClient.HandlePushNotificationQueryResponse(publicKey, msg.ParsedMessage.Interface().(protobuf.PushNotificationQueryResponse)); err != nil {
|
2020-07-09 16:52:26 +00:00
|
|
|
logger.Warn("failed to handle PushNotificationQueryResponse", zap.Error(err))
|
|
|
|
}
|
|
|
|
// We continue in any case, no changes to messenger
|
|
|
|
continue
|
|
|
|
|
2020-07-06 08:54:22 +00:00
|
|
|
case protobuf.PushNotificationRequest:
|
|
|
|
logger.Debug("Received PushNotificationRequest")
|
|
|
|
if m.pushNotificationServer == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
logger.Debug("Handling PushNotificationRequest")
|
2020-08-26 05:54:00 +00:00
|
|
|
if err := m.pushNotificationServer.HandlePushNotificationRequest(publicKey, msg.ID, msg.ParsedMessage.Interface().(protobuf.PushNotificationRequest)); err != nil {
|
2020-07-06 08:54:22 +00:00
|
|
|
logger.Warn("failed to handle PushNotificationRequest", zap.Error(err))
|
|
|
|
}
|
|
|
|
// We continue in any case, no changes to messenger
|
|
|
|
continue
|
2020-07-24 13:47:58 +00:00
|
|
|
case protobuf.EmojiReaction:
|
2020-07-25 11:46:43 +00:00
|
|
|
logger.Debug("Handling EmojiReaction")
|
2020-07-27 10:13:22 +00:00
|
|
|
err = m.handler.HandleEmojiReaction(messageState, msg.ParsedMessage.Interface().(protobuf.EmojiReaction))
|
2020-07-24 13:47:58 +00:00
|
|
|
if err != nil {
|
|
|
|
logger.Warn("failed to handle EmojiReaction", zap.Error(err))
|
|
|
|
continue
|
|
|
|
}
|
2020-08-07 13:49:37 +00:00
|
|
|
case protobuf.GroupChatInvitation:
|
|
|
|
logger.Debug("Handling GroupChatInvitation")
|
|
|
|
err = m.handler.HandleGroupChatInvitation(messageState, msg.ParsedMessage.Interface().(protobuf.GroupChatInvitation))
|
|
|
|
if err != nil {
|
|
|
|
logger.Warn("failed to handle GroupChatInvitation", zap.Error(err))
|
|
|
|
continue
|
|
|
|
}
|
2020-07-06 08:54:22 +00:00
|
|
|
|
2019-12-02 15:34:05 +00:00
|
|
|
default:
|
2020-07-07 13:55:24 +00:00
|
|
|
// Check if is an encrypted PushNotificationRegistration
|
|
|
|
if msg.Type == protobuf.ApplicationMetadataMessage_PUSH_NOTIFICATION_REGISTRATION {
|
|
|
|
logger.Debug("Received PushNotificationRegistration")
|
|
|
|
if m.pushNotificationServer == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
logger.Debug("Handling PushNotificationRegistration")
|
2020-07-27 10:13:22 +00:00
|
|
|
if err := m.pushNotificationServer.HandlePushNotificationRegistration(publicKey, msg.ParsedMessage.Interface().([]byte)); err != nil {
|
2020-07-07 13:55:24 +00:00
|
|
|
logger.Warn("failed to handle PushNotificationRegistration", zap.Error(err))
|
|
|
|
}
|
|
|
|
// We continue in any case, no changes to messenger
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-07-27 10:13:22 +00:00
|
|
|
logger.Debug("message not handled", zap.Any("messageType", reflect.TypeOf(msg.ParsedMessage.Interface())))
|
2020-01-28 11:16:28 +00:00
|
|
|
|
2019-11-15 08:52:28 +00:00
|
|
|
}
|
2020-08-18 15:07:48 +00:00
|
|
|
} else {
|
|
|
|
logger.Debug("parsed message is nil")
|
2019-11-15 08:52:28 +00:00
|
|
|
}
|
|
|
|
}
|
2019-09-02 09:29:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-17 11:22:38 +00:00
|
|
|
var contactsToSave []*Contact
|
2020-01-10 18:59:01 +00:00
|
|
|
for id := range messageState.ModifiedContacts {
|
2020-04-17 11:22:38 +00:00
|
|
|
contact := messageState.AllContacts[id]
|
|
|
|
if contact != nil {
|
|
|
|
// We save all contacts so we can pull back name/image,
|
|
|
|
// but we only send to client those
|
|
|
|
// that have some custom fields
|
|
|
|
contactsToSave = append(contactsToSave, contact)
|
|
|
|
if contact.HasCustomFields() {
|
|
|
|
messageState.Response.Contacts = append(messageState.Response.Contacts, contact)
|
|
|
|
}
|
|
|
|
}
|
2019-12-02 15:34:05 +00:00
|
|
|
}
|
|
|
|
|
2020-05-20 12:16:12 +00:00
|
|
|
for id := range messageState.ModifiedChats {
|
|
|
|
chat := messageState.AllChats[id]
|
|
|
|
if chat.OneToOne() {
|
|
|
|
contact, ok := m.allContacts[chat.ID]
|
|
|
|
if ok {
|
|
|
|
chat.Alias = contact.Alias
|
|
|
|
chat.Identicon = contact.Identicon
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
messageState.Response.Chats = append(messageState.Response.Chats, chat)
|
|
|
|
}
|
|
|
|
|
2020-02-10 11:22:37 +00:00
|
|
|
for id := range messageState.ModifiedInstallations {
|
2020-01-10 18:59:01 +00:00
|
|
|
installation := messageState.AllInstallations[id]
|
|
|
|
messageState.Response.Installations = append(messageState.Response.Installations, installation)
|
|
|
|
if installation.InstallationMetadata != nil {
|
|
|
|
err := m.setInstallationMetadata(id, installation.InstallationMetadata)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
2019-09-26 07:01:17 +00:00
|
|
|
}
|
|
|
|
|
2019-12-02 15:34:05 +00:00
|
|
|
var err error
|
2020-01-10 18:59:01 +00:00
|
|
|
if len(messageState.Response.Chats) > 0 {
|
|
|
|
err = m.saveChats(messageState.Response.Chats)
|
2019-12-02 15:34:05 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
2020-01-10 18:59:01 +00:00
|
|
|
if len(messageState.Response.Messages) > 0 {
|
|
|
|
err = m.SaveMessages(messageState.Response.Messages)
|
2019-12-02 15:34:05 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
|
|
|
}
|
2019-09-26 07:01:17 +00:00
|
|
|
|
2020-07-27 12:27:48 +00:00
|
|
|
for _, emojiReaction := range messageState.EmojiReactions {
|
|
|
|
messageState.Response.EmojiReactions = append(messageState.Response.EmojiReactions, emojiReaction)
|
|
|
|
}
|
|
|
|
|
2020-08-07 13:49:37 +00:00
|
|
|
for _, groupChatInvitation := range messageState.GroupChatInvitations {
|
|
|
|
messageState.Response.Invitations = append(messageState.Response.Invitations, groupChatInvitation)
|
|
|
|
}
|
|
|
|
|
2020-04-17 11:22:38 +00:00
|
|
|
if len(contactsToSave) > 0 {
|
|
|
|
err = m.persistence.SaveContacts(contactsToSave)
|
2019-12-02 15:34:05 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
2019-09-26 07:01:17 +00:00
|
|
|
|
2020-11-10 15:08:32 +00:00
|
|
|
newMessagesIds := map[string]struct{}{}
|
|
|
|
for _, message := range messageState.Response.Messages {
|
|
|
|
newMessagesIds[message.ID] = struct{}{}
|
|
|
|
}
|
|
|
|
|
Add replies to messages
Currently replies to messages are handled in status-react.
This causes some issues with the fact that sometimes replies might come
out of order, they might be offloaded to the database etc.
This commit changes the behavior so that status-go always returns the
replies, and in case a reply comes out of order (first the reply, later
the message being replied to), it will include in the messages the
updated message.
It also adds some fields (RTL,Replace,LineCount) to the database which
were not previously saved, resulting in some potential bugs.
The method that we use to pull replies is currently a bit naive, we just
pull all the message again from the database, but has the advantage of
being simple. It will go through performance testing to make sure
performnace are acceptable, if so I think it's reasonable to avoid some
complexity.
2020-04-08 13:42:02 +00:00
|
|
|
messagesWithResponses, err := m.pullMessagesAndResponsesFromDB(messageState.Response.Messages)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
messageState.Response.Messages = messagesWithResponses
|
|
|
|
|
2020-11-10 15:08:32 +00:00
|
|
|
for _, message := range messageState.Response.Messages {
|
|
|
|
if _, ok := newMessagesIds[message.ID]; ok {
|
|
|
|
message.New = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-10 18:59:01 +00:00
|
|
|
// Reset installations
|
|
|
|
m.modifiedInstallations = make(map[string]bool)
|
|
|
|
|
|
|
|
return messageState.Response, nil
|
2019-09-26 07:01:17 +00:00
|
|
|
}
|
|
|
|
|
2020-07-22 07:41:40 +00:00
|
|
|
// SetMailserver sets the currently used mailserver
|
2020-07-07 09:00:04 +00:00
|
|
|
func (m *Messenger) SetMailserver(peer []byte) {
|
|
|
|
m.mailserver = peer
|
|
|
|
}
|
|
|
|
|
2019-11-06 16:23:11 +00:00
|
|
|
func (m *Messenger) RequestHistoricMessages(
|
|
|
|
ctx context.Context,
|
|
|
|
from, to uint32,
|
|
|
|
cursor []byte,
|
|
|
|
) ([]byte, error) {
|
2020-07-07 09:00:04 +00:00
|
|
|
if m.mailserver == nil {
|
|
|
|
return nil, errors.New("no mailserver selected")
|
|
|
|
}
|
|
|
|
return m.transport.SendMessagesRequest(ctx, m.mailserver, from, to, cursor)
|
2019-11-06 16:23:11 +00:00
|
|
|
}
|
|
|
|
|
2019-08-29 06:33:46 +00:00
|
|
|
func (m *Messenger) LoadFilters(filters []*transport.Filter) ([]*transport.Filter, error) {
|
2019-09-02 09:29:06 +00:00
|
|
|
return m.transport.LoadFilters(filters)
|
2019-07-17 22:25:42 +00:00
|
|
|
}
|
|
|
|
|
2019-08-29 06:33:46 +00:00
|
|
|
func (m *Messenger) RemoveFilters(filters []*transport.Filter) error {
|
2019-09-02 09:29:06 +00:00
|
|
|
return m.transport.RemoveFilters(filters)
|
2019-07-17 22:25:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Messenger) ConfirmMessagesProcessed(messageIDs [][]byte) error {
|
|
|
|
for _, id := range messageIDs {
|
|
|
|
if err := m.encryptor.ConfirmMessageProcessed(id); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2019-08-06 21:50:13 +00:00
|
|
|
|
2020-09-01 13:27:01 +00:00
|
|
|
func (m *Messenger) MessageByID(id string) (*common.Message, error) {
|
2019-08-06 21:50:13 +00:00
|
|
|
return m.persistence.MessageByID(id)
|
|
|
|
}
|
|
|
|
|
2019-08-20 11:20:25 +00:00
|
|
|
func (m *Messenger) MessagesExist(ids []string) (map[string]bool, error) {
|
|
|
|
return m.persistence.MessagesExist(ids)
|
2019-08-06 21:50:13 +00:00
|
|
|
}
|
|
|
|
|
2020-09-01 13:27:01 +00:00
|
|
|
func (m *Messenger) MessageByChatID(chatID, cursor string, limit int) ([]*common.Message, string, error) {
|
2020-11-03 10:16:05 +00:00
|
|
|
chat, err := m.persistence.Chat(chatID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
if chat.Timeline() {
|
|
|
|
var chatIDs = []string{"@" + contactIDFromPublicKey(&m.identity.PublicKey)}
|
|
|
|
for _, contact := range m.allContacts {
|
|
|
|
if contact.IsAdded() {
|
|
|
|
chatIDs = append(chatIDs, "@"+contact.ID)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return m.persistence.MessageByChatIDs(chatIDs, cursor, limit)
|
|
|
|
}
|
2020-11-05 16:07:24 +00:00
|
|
|
return m.persistence.MessageByChatID(chatID, cursor, limit)
|
2019-08-06 21:50:13 +00:00
|
|
|
}
|
|
|
|
|
2020-09-01 13:27:01 +00:00
|
|
|
func (m *Messenger) SaveMessages(messages []*common.Message) error {
|
2020-05-20 12:16:12 +00:00
|
|
|
return m.persistence.SaveMessages(messages)
|
2019-08-06 21:50:13 +00:00
|
|
|
}
|
|
|
|
|
2019-08-20 11:20:25 +00:00
|
|
|
func (m *Messenger) DeleteMessage(id string) error {
|
|
|
|
return m.persistence.DeleteMessage(id)
|
2019-08-06 21:50:13 +00:00
|
|
|
}
|
|
|
|
|
2019-08-20 11:20:25 +00:00
|
|
|
func (m *Messenger) DeleteMessagesByChatID(id string) error {
|
|
|
|
return m.persistence.DeleteMessagesByChatID(id)
|
2019-08-06 21:50:13 +00:00
|
|
|
}
|
|
|
|
|
2020-04-06 12:08:53 +00:00
|
|
|
// MarkMessagesSeen marks messages with `ids` as seen in the chat `chatID`.
|
|
|
|
// It returns the number of affected messages or error. If there is an error,
|
|
|
|
// the number of affected messages is always zero.
|
|
|
|
func (m *Messenger) MarkMessagesSeen(chatID string, ids []string) (uint64, error) {
|
2019-12-02 15:34:05 +00:00
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
|
2020-04-06 12:08:53 +00:00
|
|
|
count, err := m.persistence.MarkMessagesSeen(chatID, ids)
|
2019-12-02 15:34:05 +00:00
|
|
|
if err != nil {
|
2020-04-06 12:08:53 +00:00
|
|
|
return 0, err
|
2019-12-02 15:34:05 +00:00
|
|
|
}
|
|
|
|
chat, err := m.persistence.Chat(chatID)
|
|
|
|
if err != nil {
|
2020-04-06 12:08:53 +00:00
|
|
|
return 0, err
|
2019-12-02 15:34:05 +00:00
|
|
|
}
|
|
|
|
m.allChats[chatID] = chat
|
2020-04-06 12:08:53 +00:00
|
|
|
return count, nil
|
2019-08-06 21:50:13 +00:00
|
|
|
}
|
|
|
|
|
2020-02-26 12:31:48 +00:00
|
|
|
func (m *Messenger) MarkAllRead(chatID string) error {
|
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
chat, ok := m.allChats[chatID]
|
|
|
|
if !ok {
|
|
|
|
return errors.New("chat not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
err := m.persistence.MarkAllRead(chatID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
chat.UnviewedMessagesCount = 0
|
|
|
|
m.allChats[chat.ID] = chat
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-06-26 07:46:14 +00:00
|
|
|
// MuteChat signals to the messenger that we don't want to be notified
|
|
|
|
// on new messages from this chat
|
|
|
|
func (m *Messenger) MuteChat(chatID string) error {
|
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
chat, ok := m.allChats[chatID]
|
|
|
|
if !ok {
|
|
|
|
return errors.New("chat not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
err := m.persistence.MuteChat(chatID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
chat.Muted = true
|
|
|
|
m.allChats[chat.ID] = chat
|
2020-08-18 15:07:48 +00:00
|
|
|
|
|
|
|
return m.reregisterForPushNotifications()
|
2020-06-26 07:46:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// UnmuteChat signals to the messenger that we want to be notified
|
|
|
|
// on new messages from this chat
|
|
|
|
func (m *Messenger) UnmuteChat(chatID string) error {
|
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
chat, ok := m.allChats[chatID]
|
|
|
|
if !ok {
|
|
|
|
return errors.New("chat not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
err := m.persistence.UnmuteChat(chatID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
chat.Muted = false
|
|
|
|
m.allChats[chat.ID] = chat
|
2020-08-18 15:07:48 +00:00
|
|
|
return m.reregisterForPushNotifications()
|
2020-06-26 07:46:14 +00:00
|
|
|
}
|
|
|
|
|
2019-08-06 21:50:13 +00:00
|
|
|
func (m *Messenger) UpdateMessageOutgoingStatus(id, newOutgoingStatus string) error {
|
|
|
|
return m.persistence.UpdateMessageOutgoingStatus(id, newOutgoingStatus)
|
|
|
|
}
|
2019-09-26 07:01:17 +00:00
|
|
|
|
|
|
|
// Identicon returns an identicon based on the input string
|
|
|
|
func Identicon(id string) (string, error) {
|
|
|
|
return identicon.GenerateBase64(id)
|
|
|
|
}
|
|
|
|
|
2019-11-23 17:57:05 +00:00
|
|
|
// VerifyENSNames verifies that a registered ENS name matches the expected public key
|
2020-02-05 10:09:33 +00:00
|
|
|
func (m *Messenger) VerifyENSNames(ctx context.Context, rpcEndpoint, contractAddress string) (*MessengerResponse, error) {
|
2019-12-02 15:34:05 +00:00
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
2020-02-05 10:09:33 +00:00
|
|
|
|
|
|
|
m.logger.Debug("verifying ENS Names", zap.String("endpoint", rpcEndpoint))
|
2019-11-23 17:57:05 +00:00
|
|
|
verifier := m.node.NewENSVerifier(m.logger)
|
2019-11-04 10:08:22 +00:00
|
|
|
|
2020-02-05 10:09:33 +00:00
|
|
|
var response MessengerResponse
|
|
|
|
|
|
|
|
var ensDetails []enstypes.ENSDetails
|
|
|
|
|
2020-04-09 12:48:55 +00:00
|
|
|
// Now in seconds
|
|
|
|
now := m.getTimesource().GetCurrentTime() / 1000
|
2020-02-05 10:09:33 +00:00
|
|
|
for _, contact := range m.allContacts {
|
|
|
|
if shouldENSBeVerified(contact, now) {
|
|
|
|
ensDetails = append(ensDetails, enstypes.ENSDetails{
|
|
|
|
PublicKeyString: contact.ID[2:],
|
|
|
|
Name: contact.Name,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-04 10:08:22 +00:00
|
|
|
ensResponse, err := verifier.CheckBatch(ensDetails, rpcEndpoint, contractAddress)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, details := range ensResponse {
|
2020-02-05 10:09:33 +00:00
|
|
|
contact, ok := m.allContacts["0x"+details.PublicKeyString]
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("contact must be existing")
|
|
|
|
}
|
|
|
|
|
|
|
|
m.logger.Debug("verifying ENS Name", zap.Any("details", details), zap.Any("contact", contact))
|
|
|
|
|
|
|
|
contact.ENSVerifiedAt = uint64(details.VerifiedAt)
|
|
|
|
|
2019-11-04 10:08:22 +00:00
|
|
|
if details.Error == nil {
|
|
|
|
contact.ENSVerified = details.Verified
|
2020-11-06 13:02:50 +00:00
|
|
|
// Increment count if not verified, even if no error
|
|
|
|
if !details.Verified {
|
|
|
|
contact.ENSVerificationRetries++
|
|
|
|
}
|
2020-01-17 12:39:09 +00:00
|
|
|
m.allContacts[contact.ID] = contact
|
2019-11-04 10:08:22 +00:00
|
|
|
} else {
|
|
|
|
m.logger.Warn("Failed to resolve ens name",
|
|
|
|
zap.String("name", details.Name),
|
|
|
|
zap.String("publicKey", details.PublicKeyString),
|
|
|
|
zap.Error(details.Error),
|
|
|
|
)
|
2020-04-09 12:48:55 +00:00
|
|
|
contact.ENSVerificationRetries++
|
2019-11-04 10:08:22 +00:00
|
|
|
}
|
2020-02-05 10:09:33 +00:00
|
|
|
response.Contacts = append(response.Contacts, contact)
|
2019-11-04 10:08:22 +00:00
|
|
|
}
|
|
|
|
|
2020-02-05 10:09:33 +00:00
|
|
|
if len(response.Contacts) != 0 {
|
|
|
|
err = m.persistence.SaveContacts(response.Contacts)
|
2019-11-04 10:08:22 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-05 10:09:33 +00:00
|
|
|
return &response, nil
|
2019-11-04 10:08:22 +00:00
|
|
|
}
|
|
|
|
|
2019-09-26 07:01:17 +00:00
|
|
|
// GenerateAlias name returns the generated name given a public key hex encoded prefixed with 0x
|
|
|
|
func GenerateAlias(id string) (string, error) {
|
|
|
|
return alias.GenerateFromPublicKeyString(id)
|
|
|
|
}
|
2020-01-10 18:59:01 +00:00
|
|
|
|
|
|
|
func (m *Messenger) RequestTransaction(ctx context.Context, chatID, value, contract, address string) (*MessengerResponse, error) {
|
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
|
|
|
|
var response MessengerResponse
|
|
|
|
|
|
|
|
// A valid added chat is required.
|
|
|
|
chat, ok := m.allChats[chatID]
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("Chat not found")
|
|
|
|
}
|
|
|
|
if chat.ChatType != ChatTypeOneToOne {
|
|
|
|
return nil, errors.New("Need to be a one-to-one chat")
|
|
|
|
}
|
|
|
|
|
2020-09-01 13:27:01 +00:00
|
|
|
message := &common.Message{}
|
2020-01-20 16:44:32 +00:00
|
|
|
err := extendMessageFromChat(message, chat, &m.identity.PublicKey, m.transport)
|
2020-01-10 18:59:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-07-25 11:46:43 +00:00
|
|
|
message.MessageType = protobuf.MessageType_ONE_TO_ONE
|
2020-01-10 18:59:01 +00:00
|
|
|
message.ContentType = protobuf.ChatMessage_TRANSACTION_COMMAND
|
2020-12-07 15:13:39 +00:00
|
|
|
message.Seen = true
|
2020-01-10 18:59:01 +00:00
|
|
|
message.Text = "Request transaction"
|
|
|
|
|
|
|
|
request := &protobuf.RequestTransaction{
|
|
|
|
Clock: message.Clock,
|
|
|
|
Address: address,
|
|
|
|
Value: value,
|
|
|
|
Contract: contract,
|
|
|
|
}
|
|
|
|
encodedMessage, err := proto.Marshal(request)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-07-28 13:22:22 +00:00
|
|
|
id, err := m.dispatchMessage(ctx, common.RawMessage{
|
2020-01-10 18:59:01 +00:00
|
|
|
LocalChatID: chat.ID,
|
|
|
|
Payload: encodedMessage,
|
|
|
|
MessageType: protobuf.ApplicationMetadataMessage_REQUEST_TRANSACTION,
|
|
|
|
ResendAutomatically: true,
|
|
|
|
})
|
|
|
|
|
2020-09-01 13:27:01 +00:00
|
|
|
message.CommandParameters = &common.CommandParameters{
|
2020-01-10 18:59:01 +00:00
|
|
|
ID: types.EncodeHex(id),
|
|
|
|
Value: value,
|
|
|
|
Address: address,
|
|
|
|
Contract: contract,
|
2020-09-01 13:27:01 +00:00
|
|
|
CommandState: common.CommandStateRequestTransaction,
|
2020-01-10 18:59:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
messageID := types.EncodeHex(id)
|
|
|
|
|
|
|
|
message.ID = messageID
|
|
|
|
message.CommandParameters.ID = messageID
|
|
|
|
err = message.PrepareContent()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-01-20 16:44:32 +00:00
|
|
|
err = chat.UpdateFromMessage(message, m.transport)
|
2020-01-10 18:59:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-09-01 13:27:01 +00:00
|
|
|
err = m.persistence.SaveMessages([]*common.Message{message})
|
2020-01-10 18:59:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
response.Chats = []*Chat{chat}
|
2020-09-01 13:27:01 +00:00
|
|
|
response.Messages = []*common.Message{message}
|
2020-01-10 18:59:01 +00:00
|
|
|
return &response, m.saveChat(chat)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Messenger) RequestAddressForTransaction(ctx context.Context, chatID, from, value, contract string) (*MessengerResponse, error) {
|
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
|
|
|
|
var response MessengerResponse
|
|
|
|
|
|
|
|
// A valid added chat is required.
|
|
|
|
chat, ok := m.allChats[chatID]
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("Chat not found")
|
|
|
|
}
|
|
|
|
if chat.ChatType != ChatTypeOneToOne {
|
|
|
|
return nil, errors.New("Need to be a one-to-one chat")
|
|
|
|
}
|
|
|
|
|
2020-09-01 13:27:01 +00:00
|
|
|
message := &common.Message{}
|
2020-01-20 16:44:32 +00:00
|
|
|
err := extendMessageFromChat(message, chat, &m.identity.PublicKey, m.transport)
|
2020-01-10 18:59:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-07-25 11:46:43 +00:00
|
|
|
message.MessageType = protobuf.MessageType_ONE_TO_ONE
|
2020-01-10 18:59:01 +00:00
|
|
|
message.ContentType = protobuf.ChatMessage_TRANSACTION_COMMAND
|
2020-12-07 15:13:39 +00:00
|
|
|
message.Seen = true
|
2020-01-10 18:59:01 +00:00
|
|
|
message.Text = "Request address for transaction"
|
|
|
|
|
|
|
|
request := &protobuf.RequestAddressForTransaction{
|
|
|
|
Clock: message.Clock,
|
|
|
|
Value: value,
|
|
|
|
Contract: contract,
|
|
|
|
}
|
|
|
|
encodedMessage, err := proto.Marshal(request)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-07-28 13:22:22 +00:00
|
|
|
id, err := m.dispatchMessage(ctx, common.RawMessage{
|
2020-01-10 18:59:01 +00:00
|
|
|
LocalChatID: chat.ID,
|
|
|
|
Payload: encodedMessage,
|
|
|
|
MessageType: protobuf.ApplicationMetadataMessage_REQUEST_ADDRESS_FOR_TRANSACTION,
|
|
|
|
ResendAutomatically: true,
|
|
|
|
})
|
|
|
|
|
2020-09-01 13:27:01 +00:00
|
|
|
message.CommandParameters = &common.CommandParameters{
|
2020-01-10 18:59:01 +00:00
|
|
|
ID: types.EncodeHex(id),
|
|
|
|
From: from,
|
|
|
|
Value: value,
|
|
|
|
Contract: contract,
|
2020-09-01 13:27:01 +00:00
|
|
|
CommandState: common.CommandStateRequestAddressForTransaction,
|
2020-01-10 18:59:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
messageID := types.EncodeHex(id)
|
|
|
|
|
|
|
|
message.ID = messageID
|
|
|
|
message.CommandParameters.ID = messageID
|
|
|
|
err = message.PrepareContent()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-01-20 16:44:32 +00:00
|
|
|
err = chat.UpdateFromMessage(message, m.transport)
|
2020-01-10 18:59:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-09-01 13:27:01 +00:00
|
|
|
err = m.persistence.SaveMessages([]*common.Message{message})
|
2020-01-10 18:59:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
response.Chats = []*Chat{chat}
|
2020-09-01 13:27:01 +00:00
|
|
|
response.Messages = []*common.Message{message}
|
2020-01-10 18:59:01 +00:00
|
|
|
return &response, m.saveChat(chat)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Messenger) AcceptRequestAddressForTransaction(ctx context.Context, messageID, address string) (*MessengerResponse, error) {
|
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
|
|
|
|
var response MessengerResponse
|
|
|
|
|
|
|
|
message, err := m.MessageByID(messageID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if message == nil {
|
|
|
|
return nil, errors.New("message not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
chatID := message.LocalChatID
|
|
|
|
|
|
|
|
// A valid added chat is required.
|
|
|
|
chat, ok := m.allChats[chatID]
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("Chat not found")
|
|
|
|
}
|
|
|
|
if chat.ChatType != ChatTypeOneToOne {
|
|
|
|
return nil, errors.New("Need to be a one-to-one chat")
|
|
|
|
}
|
|
|
|
|
2020-01-20 16:44:32 +00:00
|
|
|
clock, timestamp := chat.NextClockAndTimestamp(m.transport)
|
2020-01-10 18:59:01 +00:00
|
|
|
message.Clock = clock
|
|
|
|
message.WhisperTimestamp = timestamp
|
|
|
|
message.Timestamp = timestamp
|
|
|
|
message.Text = "Request address for transaction accepted"
|
2020-12-07 15:13:39 +00:00
|
|
|
message.Seen = true
|
2020-09-01 13:27:01 +00:00
|
|
|
message.OutgoingStatus = common.OutgoingStatusSending
|
2020-01-10 18:59:01 +00:00
|
|
|
|
|
|
|
// Hide previous message
|
2020-01-17 12:39:09 +00:00
|
|
|
previousMessage, err := m.persistence.MessageByCommandID(chatID, messageID)
|
2020-01-10 18:59:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if previousMessage == nil {
|
|
|
|
return nil, errors.New("No previous message found")
|
|
|
|
}
|
|
|
|
|
|
|
|
err = m.persistence.HideMessage(previousMessage.ID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
message.Replace = previousMessage.ID
|
|
|
|
|
|
|
|
request := &protobuf.AcceptRequestAddressForTransaction{
|
|
|
|
Clock: message.Clock,
|
|
|
|
Id: messageID,
|
|
|
|
Address: address,
|
|
|
|
}
|
|
|
|
encodedMessage, err := proto.Marshal(request)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-07-28 13:22:22 +00:00
|
|
|
newMessageID, err := m.dispatchMessage(ctx, common.RawMessage{
|
2020-01-10 18:59:01 +00:00
|
|
|
LocalChatID: chat.ID,
|
|
|
|
Payload: encodedMessage,
|
|
|
|
MessageType: protobuf.ApplicationMetadataMessage_ACCEPT_REQUEST_ADDRESS_FOR_TRANSACTION,
|
|
|
|
ResendAutomatically: true,
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
message.ID = types.EncodeHex(newMessageID)
|
|
|
|
message.CommandParameters.Address = address
|
2020-09-01 13:27:01 +00:00
|
|
|
message.CommandParameters.CommandState = common.CommandStateRequestAddressForTransactionAccepted
|
2020-01-10 18:59:01 +00:00
|
|
|
|
|
|
|
err = message.PrepareContent()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-01-20 16:44:32 +00:00
|
|
|
err = chat.UpdateFromMessage(message, m.transport)
|
2020-01-10 18:59:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-09-01 13:27:01 +00:00
|
|
|
err = m.persistence.SaveMessages([]*common.Message{message})
|
2020-01-10 18:59:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
response.Chats = []*Chat{chat}
|
2020-09-01 13:27:01 +00:00
|
|
|
response.Messages = []*common.Message{message}
|
2020-01-10 18:59:01 +00:00
|
|
|
return &response, m.saveChat(chat)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Messenger) DeclineRequestTransaction(ctx context.Context, messageID string) (*MessengerResponse, error) {
|
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
|
|
|
|
var response MessengerResponse
|
|
|
|
|
|
|
|
message, err := m.MessageByID(messageID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if message == nil {
|
|
|
|
return nil, errors.New("message not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
chatID := message.LocalChatID
|
|
|
|
|
|
|
|
// A valid added chat is required.
|
|
|
|
chat, ok := m.allChats[chatID]
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("Chat not found")
|
|
|
|
}
|
|
|
|
if chat.ChatType != ChatTypeOneToOne {
|
|
|
|
return nil, errors.New("Need to be a one-to-one chat")
|
|
|
|
}
|
|
|
|
|
2020-01-20 16:44:32 +00:00
|
|
|
clock, timestamp := chat.NextClockAndTimestamp(m.transport)
|
2020-01-10 18:59:01 +00:00
|
|
|
message.Clock = clock
|
|
|
|
message.WhisperTimestamp = timestamp
|
|
|
|
message.Timestamp = timestamp
|
|
|
|
message.Text = "Transaction request declined"
|
2020-12-07 15:13:39 +00:00
|
|
|
message.Seen = true
|
2020-09-01 13:27:01 +00:00
|
|
|
message.OutgoingStatus = common.OutgoingStatusSending
|
2020-01-10 18:59:01 +00:00
|
|
|
message.Replace = messageID
|
|
|
|
|
|
|
|
err = m.persistence.HideMessage(messageID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
request := &protobuf.DeclineRequestTransaction{
|
|
|
|
Clock: message.Clock,
|
|
|
|
Id: messageID,
|
|
|
|
}
|
|
|
|
encodedMessage, err := proto.Marshal(request)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-07-28 13:22:22 +00:00
|
|
|
newMessageID, err := m.dispatchMessage(ctx, common.RawMessage{
|
2020-01-10 18:59:01 +00:00
|
|
|
LocalChatID: chat.ID,
|
|
|
|
Payload: encodedMessage,
|
|
|
|
MessageType: protobuf.ApplicationMetadataMessage_DECLINE_REQUEST_TRANSACTION,
|
|
|
|
ResendAutomatically: true,
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
message.ID = types.EncodeHex(newMessageID)
|
2020-09-01 13:27:01 +00:00
|
|
|
message.CommandParameters.CommandState = common.CommandStateRequestTransactionDeclined
|
2020-01-10 18:59:01 +00:00
|
|
|
|
|
|
|
err = message.PrepareContent()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-01-20 16:44:32 +00:00
|
|
|
err = chat.UpdateFromMessage(message, m.transport)
|
2020-01-10 18:59:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-09-01 13:27:01 +00:00
|
|
|
err = m.persistence.SaveMessages([]*common.Message{message})
|
2020-01-10 18:59:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
response.Chats = []*Chat{chat}
|
2020-09-01 13:27:01 +00:00
|
|
|
response.Messages = []*common.Message{message}
|
2020-01-10 18:59:01 +00:00
|
|
|
return &response, m.saveChat(chat)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Messenger) DeclineRequestAddressForTransaction(ctx context.Context, messageID string) (*MessengerResponse, error) {
|
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
|
|
|
|
var response MessengerResponse
|
|
|
|
|
|
|
|
message, err := m.MessageByID(messageID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if message == nil {
|
|
|
|
return nil, errors.New("message not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
chatID := message.LocalChatID
|
|
|
|
|
|
|
|
// A valid added chat is required.
|
|
|
|
chat, ok := m.allChats[chatID]
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("Chat not found")
|
|
|
|
}
|
|
|
|
if chat.ChatType != ChatTypeOneToOne {
|
|
|
|
return nil, errors.New("Need to be a one-to-one chat")
|
|
|
|
}
|
|
|
|
|
2020-01-20 16:44:32 +00:00
|
|
|
clock, timestamp := chat.NextClockAndTimestamp(m.transport)
|
2020-01-10 18:59:01 +00:00
|
|
|
message.Clock = clock
|
|
|
|
message.WhisperTimestamp = timestamp
|
|
|
|
message.Timestamp = timestamp
|
|
|
|
message.Text = "Request address for transaction declined"
|
2020-12-07 15:13:39 +00:00
|
|
|
message.Seen = true
|
2020-09-01 13:27:01 +00:00
|
|
|
message.OutgoingStatus = common.OutgoingStatusSending
|
2020-01-10 18:59:01 +00:00
|
|
|
message.Replace = messageID
|
|
|
|
|
|
|
|
err = m.persistence.HideMessage(messageID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
request := &protobuf.DeclineRequestAddressForTransaction{
|
|
|
|
Clock: message.Clock,
|
|
|
|
Id: messageID,
|
|
|
|
}
|
|
|
|
encodedMessage, err := proto.Marshal(request)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-07-28 13:22:22 +00:00
|
|
|
newMessageID, err := m.dispatchMessage(ctx, common.RawMessage{
|
2020-01-10 18:59:01 +00:00
|
|
|
LocalChatID: chat.ID,
|
|
|
|
Payload: encodedMessage,
|
|
|
|
MessageType: protobuf.ApplicationMetadataMessage_DECLINE_REQUEST_ADDRESS_FOR_TRANSACTION,
|
|
|
|
ResendAutomatically: true,
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
message.ID = types.EncodeHex(newMessageID)
|
2020-09-01 13:27:01 +00:00
|
|
|
message.CommandParameters.CommandState = common.CommandStateRequestAddressForTransactionDeclined
|
2020-01-10 18:59:01 +00:00
|
|
|
|
|
|
|
err = message.PrepareContent()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-01-20 16:44:32 +00:00
|
|
|
err = chat.UpdateFromMessage(message, m.transport)
|
2020-01-10 18:59:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-09-01 13:27:01 +00:00
|
|
|
err = m.persistence.SaveMessages([]*common.Message{message})
|
2020-01-10 18:59:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
response.Chats = []*Chat{chat}
|
2020-09-01 13:27:01 +00:00
|
|
|
response.Messages = []*common.Message{message}
|
2020-01-10 18:59:01 +00:00
|
|
|
return &response, m.saveChat(chat)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Messenger) AcceptRequestTransaction(ctx context.Context, transactionHash, messageID string, signature []byte) (*MessengerResponse, error) {
|
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
|
|
|
|
var response MessengerResponse
|
|
|
|
|
|
|
|
message, err := m.MessageByID(messageID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if message == nil {
|
|
|
|
return nil, errors.New("message not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
chatID := message.LocalChatID
|
|
|
|
|
|
|
|
// A valid added chat is required.
|
|
|
|
chat, ok := m.allChats[chatID]
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("Chat not found")
|
|
|
|
}
|
|
|
|
if chat.ChatType != ChatTypeOneToOne {
|
|
|
|
return nil, errors.New("Need to be a one-to-one chat")
|
|
|
|
}
|
|
|
|
|
2020-01-20 16:44:32 +00:00
|
|
|
clock, timestamp := chat.NextClockAndTimestamp(m.transport)
|
2020-01-10 18:59:01 +00:00
|
|
|
message.Clock = clock
|
|
|
|
message.WhisperTimestamp = timestamp
|
|
|
|
message.Timestamp = timestamp
|
2020-12-07 15:13:39 +00:00
|
|
|
message.Seen = true
|
2020-02-10 11:22:37 +00:00
|
|
|
message.Text = transactionSentTxt
|
2020-09-01 13:27:01 +00:00
|
|
|
message.OutgoingStatus = common.OutgoingStatusSending
|
2020-01-10 18:59:01 +00:00
|
|
|
|
|
|
|
// Hide previous message
|
2020-01-17 12:39:09 +00:00
|
|
|
previousMessage, err := m.persistence.MessageByCommandID(chatID, messageID)
|
2020-10-08 10:46:03 +00:00
|
|
|
if err != nil && err != common.ErrRecordNotFound {
|
2020-01-10 18:59:01 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if previousMessage != nil {
|
|
|
|
err = m.persistence.HideMessage(previousMessage.ID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
message.Replace = previousMessage.ID
|
|
|
|
}
|
|
|
|
|
|
|
|
err = m.persistence.HideMessage(messageID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
request := &protobuf.SendTransaction{
|
|
|
|
Clock: message.Clock,
|
|
|
|
Id: messageID,
|
|
|
|
TransactionHash: transactionHash,
|
|
|
|
Signature: signature,
|
|
|
|
}
|
|
|
|
encodedMessage, err := proto.Marshal(request)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-07-28 13:22:22 +00:00
|
|
|
newMessageID, err := m.dispatchMessage(ctx, common.RawMessage{
|
2020-01-10 18:59:01 +00:00
|
|
|
LocalChatID: chat.ID,
|
|
|
|
Payload: encodedMessage,
|
|
|
|
MessageType: protobuf.ApplicationMetadataMessage_SEND_TRANSACTION,
|
|
|
|
ResendAutomatically: true,
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
message.ID = types.EncodeHex(newMessageID)
|
|
|
|
message.CommandParameters.TransactionHash = transactionHash
|
|
|
|
message.CommandParameters.Signature = signature
|
2020-09-01 13:27:01 +00:00
|
|
|
message.CommandParameters.CommandState = common.CommandStateTransactionSent
|
2020-01-10 18:59:01 +00:00
|
|
|
|
|
|
|
err = message.PrepareContent()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-01-20 16:44:32 +00:00
|
|
|
err = chat.UpdateFromMessage(message, m.transport)
|
2020-01-10 18:59:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-09-01 13:27:01 +00:00
|
|
|
err = m.persistence.SaveMessages([]*common.Message{message})
|
2020-01-10 18:59:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
response.Chats = []*Chat{chat}
|
2020-09-01 13:27:01 +00:00
|
|
|
response.Messages = []*common.Message{message}
|
2020-01-10 18:59:01 +00:00
|
|
|
return &response, m.saveChat(chat)
|
|
|
|
}
|
|
|
|
|
2020-01-15 07:25:09 +00:00
|
|
|
func (m *Messenger) SendTransaction(ctx context.Context, chatID, value, contract, transactionHash string, signature []byte) (*MessengerResponse, error) {
|
2020-01-10 18:59:01 +00:00
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
|
|
|
|
var response MessengerResponse
|
|
|
|
|
|
|
|
// A valid added chat is required.
|
|
|
|
chat, ok := m.allChats[chatID]
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("Chat not found")
|
|
|
|
}
|
|
|
|
if chat.ChatType != ChatTypeOneToOne {
|
|
|
|
return nil, errors.New("Need to be a one-to-one chat")
|
|
|
|
}
|
|
|
|
|
2020-09-01 13:27:01 +00:00
|
|
|
message := &common.Message{}
|
2020-01-20 16:44:32 +00:00
|
|
|
err := extendMessageFromChat(message, chat, &m.identity.PublicKey, m.transport)
|
2020-01-10 18:59:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-07-25 11:46:43 +00:00
|
|
|
message.MessageType = protobuf.MessageType_ONE_TO_ONE
|
2020-01-10 18:59:01 +00:00
|
|
|
message.ContentType = protobuf.ChatMessage_TRANSACTION_COMMAND
|
|
|
|
message.LocalChatID = chatID
|
|
|
|
|
2020-01-20 16:44:32 +00:00
|
|
|
clock, timestamp := chat.NextClockAndTimestamp(m.transport)
|
2020-01-10 18:59:01 +00:00
|
|
|
message.Clock = clock
|
|
|
|
message.WhisperTimestamp = timestamp
|
2020-12-07 15:13:39 +00:00
|
|
|
message.Seen = true
|
2020-01-10 18:59:01 +00:00
|
|
|
message.Timestamp = timestamp
|
2020-02-10 11:22:37 +00:00
|
|
|
message.Text = transactionSentTxt
|
2020-01-10 18:59:01 +00:00
|
|
|
|
|
|
|
request := &protobuf.SendTransaction{
|
|
|
|
Clock: message.Clock,
|
|
|
|
TransactionHash: transactionHash,
|
|
|
|
Signature: signature,
|
|
|
|
}
|
|
|
|
encodedMessage, err := proto.Marshal(request)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-07-28 13:22:22 +00:00
|
|
|
newMessageID, err := m.dispatchMessage(ctx, common.RawMessage{
|
2020-01-10 18:59:01 +00:00
|
|
|
LocalChatID: chat.ID,
|
|
|
|
Payload: encodedMessage,
|
|
|
|
MessageType: protobuf.ApplicationMetadataMessage_SEND_TRANSACTION,
|
|
|
|
ResendAutomatically: true,
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
message.ID = types.EncodeHex(newMessageID)
|
2020-09-01 13:27:01 +00:00
|
|
|
message.CommandParameters = &common.CommandParameters{
|
2020-01-10 18:59:01 +00:00
|
|
|
TransactionHash: transactionHash,
|
2020-01-15 07:25:09 +00:00
|
|
|
Value: value,
|
|
|
|
Contract: contract,
|
2020-01-10 18:59:01 +00:00
|
|
|
Signature: signature,
|
2020-09-01 13:27:01 +00:00
|
|
|
CommandState: common.CommandStateTransactionSent,
|
2020-01-10 18:59:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
err = message.PrepareContent()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-01-20 16:44:32 +00:00
|
|
|
err = chat.UpdateFromMessage(message, m.transport)
|
2020-01-10 18:59:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-09-01 13:27:01 +00:00
|
|
|
err = m.persistence.SaveMessages([]*common.Message{message})
|
2020-01-10 18:59:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
response.Chats = []*Chat{chat}
|
2020-09-01 13:27:01 +00:00
|
|
|
response.Messages = []*common.Message{message}
|
2020-01-10 18:59:01 +00:00
|
|
|
return &response, m.saveChat(chat)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Messenger) ValidateTransactions(ctx context.Context, addresses []types.Address) (*MessengerResponse, error) {
|
|
|
|
if m.verifyTransactionClient == nil {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
|
|
|
|
modifiedChats := make(map[string]bool)
|
|
|
|
|
|
|
|
logger := m.logger.With(zap.String("site", "ValidateTransactions"))
|
|
|
|
logger.Debug("Validating transactions")
|
|
|
|
txs, err := m.persistence.TransactionsToValidate()
|
|
|
|
if err != nil {
|
|
|
|
logger.Error("Error pulling", zap.Error(err))
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
logger.Debug("Txs", zap.Int("count", len(txs)), zap.Any("txs", txs))
|
|
|
|
var response MessengerResponse
|
|
|
|
validator := NewTransactionValidator(addresses, m.persistence, m.verifyTransactionClient, m.logger)
|
|
|
|
responses, err := validator.ValidateTransactions(ctx)
|
|
|
|
if err != nil {
|
|
|
|
logger.Error("Error validating", zap.Error(err))
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
for _, validationResult := range responses {
|
2020-09-01 13:27:01 +00:00
|
|
|
var message *common.Message
|
2020-01-15 07:25:09 +00:00
|
|
|
chatID := contactIDFromPublicKey(validationResult.Transaction.From)
|
2020-01-10 18:59:01 +00:00
|
|
|
chat, ok := m.allChats[chatID]
|
|
|
|
if !ok {
|
2020-01-20 16:44:32 +00:00
|
|
|
chat = OneToOneFromPublicKey(validationResult.Transaction.From, m.transport)
|
2020-01-10 18:59:01 +00:00
|
|
|
}
|
|
|
|
if validationResult.Message != nil {
|
|
|
|
message = validationResult.Message
|
|
|
|
} else {
|
2020-09-01 13:27:01 +00:00
|
|
|
message = &common.Message{}
|
2020-01-20 16:44:32 +00:00
|
|
|
err := extendMessageFromChat(message, chat, &m.identity.PublicKey, m.transport)
|
2020-01-10 18:59:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-25 11:46:43 +00:00
|
|
|
message.MessageType = protobuf.MessageType_ONE_TO_ONE
|
2020-01-10 18:59:01 +00:00
|
|
|
message.ContentType = protobuf.ChatMessage_TRANSACTION_COMMAND
|
|
|
|
message.LocalChatID = chatID
|
|
|
|
message.OutgoingStatus = ""
|
|
|
|
|
2020-01-20 16:44:32 +00:00
|
|
|
clock, timestamp := chat.NextClockAndTimestamp(m.transport)
|
2020-01-10 18:59:01 +00:00
|
|
|
message.Clock = clock
|
|
|
|
message.Timestamp = timestamp
|
|
|
|
message.WhisperTimestamp = timestamp
|
|
|
|
message.Text = "Transaction received"
|
2020-04-06 12:08:53 +00:00
|
|
|
message.Seen = false
|
2020-01-10 18:59:01 +00:00
|
|
|
|
|
|
|
message.ID = validationResult.Transaction.MessageID
|
|
|
|
if message.CommandParameters == nil {
|
2020-09-01 13:27:01 +00:00
|
|
|
message.CommandParameters = &common.CommandParameters{}
|
2020-01-10 18:59:01 +00:00
|
|
|
} else {
|
|
|
|
message.CommandParameters = validationResult.Message.CommandParameters
|
|
|
|
}
|
|
|
|
|
|
|
|
message.CommandParameters.Value = validationResult.Value
|
|
|
|
message.CommandParameters.Contract = validationResult.Contract
|
|
|
|
message.CommandParameters.Address = validationResult.Address
|
2020-09-01 13:27:01 +00:00
|
|
|
message.CommandParameters.CommandState = common.CommandStateTransactionSent
|
2020-01-10 18:59:01 +00:00
|
|
|
message.CommandParameters.TransactionHash = validationResult.Transaction.TransactionHash
|
|
|
|
|
|
|
|
err = message.PrepareContent()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-01-20 16:44:32 +00:00
|
|
|
err = chat.UpdateFromMessage(message, m.transport)
|
2020-01-10 18:59:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-01-17 12:39:09 +00:00
|
|
|
if len(message.CommandParameters.ID) != 0 {
|
|
|
|
// Hide previous message
|
|
|
|
previousMessage, err := m.persistence.MessageByCommandID(chatID, message.CommandParameters.ID)
|
2020-10-08 10:46:03 +00:00
|
|
|
if err != nil && err != common.ErrRecordNotFound {
|
2020-01-10 18:59:01 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2020-01-17 12:39:09 +00:00
|
|
|
|
|
|
|
if previousMessage != nil {
|
|
|
|
err = m.persistence.HideMessage(previousMessage.ID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
message.Replace = previousMessage.ID
|
|
|
|
}
|
2020-01-10 18:59:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
response.Messages = append(response.Messages, message)
|
|
|
|
m.allChats[chat.ID] = chat
|
|
|
|
modifiedChats[chat.ID] = true
|
|
|
|
|
|
|
|
}
|
2020-02-10 11:22:37 +00:00
|
|
|
for id := range modifiedChats {
|
2020-01-10 18:59:01 +00:00
|
|
|
response.Chats = append(response.Chats, m.allChats[id])
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(response.Messages) > 0 {
|
|
|
|
err = m.SaveMessages(response.Messages)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return &response, nil
|
|
|
|
}
|
2020-01-20 16:44:32 +00:00
|
|
|
|
Add replies to messages
Currently replies to messages are handled in status-react.
This causes some issues with the fact that sometimes replies might come
out of order, they might be offloaded to the database etc.
This commit changes the behavior so that status-go always returns the
replies, and in case a reply comes out of order (first the reply, later
the message being replied to), it will include in the messages the
updated message.
It also adds some fields (RTL,Replace,LineCount) to the database which
were not previously saved, resulting in some potential bugs.
The method that we use to pull replies is currently a bit naive, we just
pull all the message again from the database, but has the advantage of
being simple. It will go through performance testing to make sure
performnace are acceptable, if so I think it's reasonable to avoid some
complexity.
2020-04-08 13:42:02 +00:00
|
|
|
// pullMessagesAndResponsesFromDB pulls all the messages and the one that have
|
|
|
|
// been replied to from the database
|
2020-09-01 13:27:01 +00:00
|
|
|
func (m *Messenger) pullMessagesAndResponsesFromDB(messages []*common.Message) ([]*common.Message, error) {
|
Add replies to messages
Currently replies to messages are handled in status-react.
This causes some issues with the fact that sometimes replies might come
out of order, they might be offloaded to the database etc.
This commit changes the behavior so that status-go always returns the
replies, and in case a reply comes out of order (first the reply, later
the message being replied to), it will include in the messages the
updated message.
It also adds some fields (RTL,Replace,LineCount) to the database which
were not previously saved, resulting in some potential bugs.
The method that we use to pull replies is currently a bit naive, we just
pull all the message again from the database, but has the advantage of
being simple. It will go through performance testing to make sure
performnace are acceptable, if so I think it's reasonable to avoid some
complexity.
2020-04-08 13:42:02 +00:00
|
|
|
var messageIDs []string
|
|
|
|
for _, message := range messages {
|
|
|
|
messageIDs = append(messageIDs, message.ID)
|
|
|
|
if len(message.ResponseTo) != 0 {
|
|
|
|
messageIDs = append(messageIDs, message.ResponseTo)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
// We pull from the database all the messages & replies involved,
|
|
|
|
// so we let the db build the correct messages
|
|
|
|
return m.persistence.MessagesByIDs(messageIDs)
|
|
|
|
}
|
|
|
|
|
2020-03-20 08:32:13 +00:00
|
|
|
func (m *Messenger) SignMessage(message string) ([]byte, error) {
|
|
|
|
hash := crypto.TextHash([]byte(message))
|
|
|
|
return crypto.Sign(hash, m.identity)
|
|
|
|
}
|
|
|
|
|
2020-02-07 11:30:26 +00:00
|
|
|
func (m *Messenger) getTimesource() TimeSource {
|
2020-01-20 16:44:32 +00:00
|
|
|
return m.transport
|
|
|
|
}
|
2020-02-07 11:30:26 +00:00
|
|
|
|
2020-07-22 07:41:40 +00:00
|
|
|
// AddPushNotificationsServer adds a push notification server
|
2020-08-20 09:05:39 +00:00
|
|
|
func (m *Messenger) AddPushNotificationsServer(ctx context.Context, publicKey *ecdsa.PublicKey, serverType pushnotificationclient.ServerType) error {
|
2020-07-07 09:00:04 +00:00
|
|
|
if m.pushNotificationClient == nil {
|
|
|
|
return errors.New("push notification client not enabled")
|
|
|
|
}
|
2020-08-20 09:05:39 +00:00
|
|
|
return m.pushNotificationClient.AddPushNotificationsServer(publicKey, serverType)
|
2020-07-07 09:00:04 +00:00
|
|
|
}
|
|
|
|
|
2020-07-17 11:41:49 +00:00
|
|
|
// RemovePushNotificationServer removes a push notification server
|
|
|
|
func (m *Messenger) RemovePushNotificationServer(ctx context.Context, publicKey *ecdsa.PublicKey) error {
|
|
|
|
if m.pushNotificationClient == nil {
|
|
|
|
return errors.New("push notification client not enabled")
|
|
|
|
}
|
|
|
|
return m.pushNotificationClient.RemovePushNotificationServer(publicKey)
|
|
|
|
}
|
|
|
|
|
2020-07-22 07:41:40 +00:00
|
|
|
// UnregisterFromPushNotifications unregister from any server
|
2020-07-15 12:43:15 +00:00
|
|
|
func (m *Messenger) UnregisterFromPushNotifications(ctx context.Context) error {
|
2020-07-16 07:45:42 +00:00
|
|
|
return m.pushNotificationClient.Unregister()
|
2020-07-15 12:43:15 +00:00
|
|
|
}
|
|
|
|
|
2020-07-22 07:41:40 +00:00
|
|
|
// DisableSendingPushNotifications signals the client not to send any push notification
|
2020-07-15 12:43:15 +00:00
|
|
|
func (m *Messenger) DisableSendingPushNotifications() error {
|
|
|
|
if m.pushNotificationClient == nil {
|
|
|
|
return errors.New("push notification client not enabled")
|
|
|
|
}
|
|
|
|
m.pushNotificationClient.DisableSending()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-07-22 07:41:40 +00:00
|
|
|
// EnableSendingPushNotifications signals the client to send push notifications
|
2020-07-15 12:43:15 +00:00
|
|
|
func (m *Messenger) EnableSendingPushNotifications() error {
|
|
|
|
if m.pushNotificationClient == nil {
|
|
|
|
return errors.New("push notification client not enabled")
|
|
|
|
}
|
|
|
|
m.pushNotificationClient.EnableSending()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-09-02 14:11:16 +00:00
|
|
|
func (m *Messenger) pushNotificationOptions() *pushnotificationclient.RegistrationOptions {
|
2020-07-07 09:00:04 +00:00
|
|
|
var contactIDs []*ecdsa.PublicKey
|
|
|
|
var mutedChatIDs []string
|
2020-09-03 07:19:46 +00:00
|
|
|
var publicChatIDs []string
|
2020-07-07 09:00:04 +00:00
|
|
|
|
|
|
|
for _, contact := range m.allContacts {
|
2020-09-03 09:54:05 +00:00
|
|
|
if contact.IsAdded() && !contact.IsBlocked() {
|
2020-07-07 09:00:04 +00:00
|
|
|
pk, err := contact.PublicKey()
|
|
|
|
if err != nil {
|
|
|
|
m.logger.Warn("could not parse contact public key")
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
contactIDs = append(contactIDs, pk)
|
|
|
|
} else if contact.IsBlocked() {
|
|
|
|
mutedChatIDs = append(mutedChatIDs, contact.ID)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, chat := range m.allChats {
|
|
|
|
if chat.Muted {
|
|
|
|
mutedChatIDs = append(mutedChatIDs, chat.ID)
|
|
|
|
}
|
2020-09-03 07:19:46 +00:00
|
|
|
if chat.Active && chat.Public() {
|
|
|
|
publicChatIDs = append(publicChatIDs, chat.ID)
|
|
|
|
}
|
2020-07-07 09:00:04 +00:00
|
|
|
|
|
|
|
}
|
2020-09-02 14:11:16 +00:00
|
|
|
return &pushnotificationclient.RegistrationOptions{
|
2020-09-03 07:19:46 +00:00
|
|
|
ContactIDs: contactIDs,
|
|
|
|
MutedChatIDs: mutedChatIDs,
|
|
|
|
PublicChatIDs: publicChatIDs,
|
2020-09-02 14:11:16 +00:00
|
|
|
}
|
2020-07-17 12:29:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// RegisterForPushNotification register deviceToken with any push notification server enabled
|
2020-07-30 08:24:30 +00:00
|
|
|
func (m *Messenger) RegisterForPushNotifications(ctx context.Context, deviceToken, apnTopic string, tokenType protobuf.PushNotificationRegistration_TokenType) error {
|
2020-07-17 12:29:51 +00:00
|
|
|
if m.pushNotificationClient == nil {
|
|
|
|
return errors.New("push notification client not enabled")
|
|
|
|
}
|
2020-07-20 13:58:54 +00:00
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
2020-07-17 12:29:51 +00:00
|
|
|
|
2020-09-02 14:11:16 +00:00
|
|
|
err := m.pushNotificationClient.Register(deviceToken, apnTopic, tokenType, m.pushNotificationOptions())
|
2020-08-18 15:07:48 +00:00
|
|
|
if err != nil {
|
|
|
|
m.logger.Error("failed to register for push notifications", zap.Error(err))
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
2020-07-07 09:00:04 +00:00
|
|
|
}
|
|
|
|
|
2020-07-22 07:41:40 +00:00
|
|
|
// RegisteredForPushNotifications returns whether we successfully registered with all the servers
|
2020-07-16 08:36:17 +00:00
|
|
|
func (m *Messenger) RegisteredForPushNotifications() (bool, error) {
|
|
|
|
if m.pushNotificationClient == nil {
|
|
|
|
return false, errors.New("no push notification client")
|
|
|
|
}
|
|
|
|
return m.pushNotificationClient.Registered()
|
|
|
|
}
|
|
|
|
|
2020-07-22 07:41:40 +00:00
|
|
|
// EnablePushNotificationsFromContactsOnly is used to indicate that we want to received push notifications only from contacts
|
2020-07-17 11:41:49 +00:00
|
|
|
func (m *Messenger) EnablePushNotificationsFromContactsOnly() error {
|
|
|
|
if m.pushNotificationClient == nil {
|
|
|
|
return errors.New("no push notification client")
|
|
|
|
}
|
2020-07-20 13:58:54 +00:00
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
2020-07-17 11:41:49 +00:00
|
|
|
|
2020-09-02 14:11:16 +00:00
|
|
|
return m.pushNotificationClient.EnablePushNotificationsFromContactsOnly(m.pushNotificationOptions())
|
2020-07-17 11:41:49 +00:00
|
|
|
}
|
|
|
|
|
2020-07-22 07:41:40 +00:00
|
|
|
// DisablePushNotificationsFromContactsOnly is used to indicate that we want to received push notifications from anyone
|
2020-07-17 11:41:49 +00:00
|
|
|
func (m *Messenger) DisablePushNotificationsFromContactsOnly() error {
|
|
|
|
if m.pushNotificationClient == nil {
|
|
|
|
return errors.New("no push notification client")
|
|
|
|
}
|
2020-07-20 13:58:54 +00:00
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
2020-07-17 11:41:49 +00:00
|
|
|
|
2020-09-02 14:11:16 +00:00
|
|
|
return m.pushNotificationClient.DisablePushNotificationsFromContactsOnly(m.pushNotificationOptions())
|
2020-07-17 11:41:49 +00:00
|
|
|
}
|
|
|
|
|
2020-09-03 07:30:03 +00:00
|
|
|
// EnablePushNotificationsBlockMentions is used to indicate that we dont want to received push notifications for mentions
|
|
|
|
func (m *Messenger) EnablePushNotificationsBlockMentions() error {
|
|
|
|
if m.pushNotificationClient == nil {
|
|
|
|
return errors.New("no push notification client")
|
|
|
|
}
|
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
|
|
|
|
return m.pushNotificationClient.EnablePushNotificationsBlockMentions(m.pushNotificationOptions())
|
|
|
|
}
|
|
|
|
|
|
|
|
// DisablePushNotificationsBlockMentions is used to indicate that we want to received push notifications for mentions
|
|
|
|
func (m *Messenger) DisablePushNotificationsBlockMentions() error {
|
|
|
|
if m.pushNotificationClient == nil {
|
|
|
|
return errors.New("no push notification client")
|
|
|
|
}
|
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
|
|
|
|
return m.pushNotificationClient.DisablePushNotificationsBlockMentions(m.pushNotificationOptions())
|
|
|
|
}
|
|
|
|
|
2020-08-20 09:05:39 +00:00
|
|
|
// GetPushNotificationsServers returns the servers used for push notifications
|
|
|
|
func (m *Messenger) GetPushNotificationsServers() ([]*pushnotificationclient.PushNotificationServer, error) {
|
2020-07-16 08:36:17 +00:00
|
|
|
if m.pushNotificationClient == nil {
|
|
|
|
return nil, errors.New("no push notification client")
|
|
|
|
}
|
|
|
|
return m.pushNotificationClient.GetServers()
|
|
|
|
}
|
|
|
|
|
2020-07-22 07:41:40 +00:00
|
|
|
// StartPushNotificationsServer initialize and start a push notification server, using the current messenger identity key
|
|
|
|
func (m *Messenger) StartPushNotificationsServer() error {
|
2020-07-14 14:07:19 +00:00
|
|
|
if m.pushNotificationServer == nil {
|
2020-07-22 07:41:40 +00:00
|
|
|
pushNotificationServerPersistence := pushnotificationserver.NewSQLitePersistence(m.database)
|
|
|
|
config := &pushnotificationserver.Config{
|
2020-08-20 07:26:00 +00:00
|
|
|
Enabled: true,
|
2020-07-14 14:07:19 +00:00
|
|
|
Logger: m.logger,
|
|
|
|
Identity: m.identity,
|
|
|
|
}
|
2020-07-22 07:41:40 +00:00
|
|
|
m.pushNotificationServer = pushnotificationserver.New(config, pushNotificationServerPersistence, m.processor)
|
2020-07-14 14:07:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return m.pushNotificationServer.Start()
|
|
|
|
}
|
|
|
|
|
2020-07-22 07:41:40 +00:00
|
|
|
// StopPushNotificationServer stops the push notification server if running
|
|
|
|
func (m *Messenger) StopPushNotificationsServer() error {
|
2020-07-14 14:07:19 +00:00
|
|
|
m.pushNotificationServer = nil
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-05-20 12:16:12 +00:00
|
|
|
func generateAliasAndIdenticon(pk string) (string, string, error) {
|
|
|
|
identicon, err := identicon.GenerateBase64(pk)
|
|
|
|
if err != nil {
|
|
|
|
return "", "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
name, err := alias.GenerateFromPublicKeyString(pk)
|
|
|
|
if err != nil {
|
|
|
|
return "", "", err
|
|
|
|
}
|
|
|
|
return name, identicon, nil
|
|
|
|
|
|
|
|
}
|
2020-07-20 17:06:38 +00:00
|
|
|
|
2020-07-27 12:27:48 +00:00
|
|
|
func (m *Messenger) SendEmojiReaction(ctx context.Context, chatID, messageID string, emojiID protobuf.EmojiReaction_Type) (*MessengerResponse, error) {
|
2020-07-22 00:38:01 +00:00
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
|
2020-07-20 17:06:38 +00:00
|
|
|
var response MessengerResponse
|
|
|
|
|
|
|
|
chat, ok := m.allChats[chatID]
|
|
|
|
if !ok {
|
|
|
|
return nil, ErrChatNotFound
|
|
|
|
}
|
|
|
|
clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
|
|
|
|
|
2020-07-26 22:06:58 +00:00
|
|
|
emojiR := &EmojiReaction{
|
|
|
|
EmojiReaction: protobuf.EmojiReaction{
|
|
|
|
Clock: clock,
|
|
|
|
MessageId: messageID,
|
|
|
|
ChatId: chatID,
|
2020-07-28 08:02:51 +00:00
|
|
|
Type: emojiID,
|
2020-07-26 22:06:58 +00:00
|
|
|
},
|
2020-07-28 07:53:32 +00:00
|
|
|
LocalChatID: chatID,
|
|
|
|
From: types.EncodeHex(crypto.FromECDSAPub(&m.identity.PublicKey)),
|
2020-07-20 17:06:38 +00:00
|
|
|
}
|
2020-07-26 22:06:58 +00:00
|
|
|
encodedMessage, err := m.encodeChatEntity(chat, emojiR)
|
2020-07-20 17:06:38 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-07-28 13:26:34 +00:00
|
|
|
_, err = m.dispatchMessage(ctx, common.RawMessage{
|
2020-07-27 12:27:48 +00:00
|
|
|
LocalChatID: chatID,
|
|
|
|
Payload: encodedMessage,
|
|
|
|
MessageType: protobuf.ApplicationMetadataMessage_EMOJI_REACTION,
|
|
|
|
// Don't resend using datasync, that would create quite a lot
|
|
|
|
// of traffic if clicking too eagelry
|
|
|
|
ResendAutomatically: false,
|
2020-07-20 17:06:38 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
response.EmojiReactions = []*EmojiReaction{emojiR}
|
|
|
|
response.Chats = []*Chat{chat}
|
|
|
|
|
2020-07-21 23:42:55 +00:00
|
|
|
err = m.persistence.SaveEmojiReaction(emojiR)
|
|
|
|
if err != nil {
|
2020-12-15 14:43:41 +00:00
|
|
|
return nil, errors.Wrap(err, "Can't save emoji reaction in db")
|
2020-07-21 23:42:55 +00:00
|
|
|
}
|
2020-07-20 17:06:38 +00:00
|
|
|
|
|
|
|
return &response, nil
|
|
|
|
}
|
|
|
|
|
2020-07-27 15:57:01 +00:00
|
|
|
func (m *Messenger) EmojiReactionsByChatID(chatID string, cursor string, limit int) ([]*EmojiReaction, error) {
|
2020-11-16 11:54:39 +00:00
|
|
|
chat, err := m.persistence.Chat(chatID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if chat.Timeline() {
|
|
|
|
var chatIDs = []string{"@" + contactIDFromPublicKey(&m.identity.PublicKey)}
|
|
|
|
for _, contact := range m.allContacts {
|
|
|
|
if contact.IsAdded() {
|
|
|
|
chatIDs = append(chatIDs, "@"+contact.ID)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return m.persistence.EmojiReactionsByChatIDs(chatIDs, cursor, limit)
|
|
|
|
}
|
2020-07-27 15:57:01 +00:00
|
|
|
return m.persistence.EmojiReactionsByChatID(chatID, cursor, limit)
|
|
|
|
}
|
|
|
|
|
2020-07-22 00:38:01 +00:00
|
|
|
func (m *Messenger) SendEmojiReactionRetraction(ctx context.Context, emojiReactionID string) (*MessengerResponse, error) {
|
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
|
2020-07-27 12:27:48 +00:00
|
|
|
emojiR, err := m.persistence.EmojiReactionByID(emojiReactionID)
|
2020-07-22 00:38:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-07-22 00:57:44 +00:00
|
|
|
// Check that the sender is the key owner
|
|
|
|
pk := types.EncodeHex(crypto.FromECDSAPub(&m.identity.PublicKey))
|
2020-07-27 12:27:48 +00:00
|
|
|
if emojiR.From != pk {
|
2020-07-22 14:54:24 +00:00
|
|
|
return nil, errors.Errorf("identity mismatch, "+
|
|
|
|
"emoji reactions can only be retracted by the reaction sender, "+
|
2020-07-22 00:57:44 +00:00
|
|
|
"emoji reaction sent by '%s', current identity '%s'",
|
2020-07-27 12:27:48 +00:00
|
|
|
emojiR.From, pk,
|
2020-07-22 00:57:44 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get chat and clock
|
2020-07-27 12:27:48 +00:00
|
|
|
chat, ok := m.allChats[emojiR.GetChatId()]
|
2020-07-22 00:38:01 +00:00
|
|
|
if !ok {
|
|
|
|
return nil, ErrChatNotFound
|
|
|
|
}
|
|
|
|
clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
|
|
|
|
|
2020-07-27 12:27:48 +00:00
|
|
|
// Update the relevant fields
|
|
|
|
emojiR.Clock = clock
|
|
|
|
emojiR.Retracted = true
|
|
|
|
|
|
|
|
encodedMessage, err := m.encodeChatEntity(chat, emojiR)
|
2020-07-22 00:38:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Send the marshalled EmojiReactionRetraction protobuf
|
2020-07-28 13:26:34 +00:00
|
|
|
_, err = m.dispatchMessage(ctx, common.RawMessage{
|
2020-07-27 12:27:48 +00:00
|
|
|
LocalChatID: emojiR.GetChatId(),
|
|
|
|
Payload: encodedMessage,
|
|
|
|
MessageType: protobuf.ApplicationMetadataMessage_EMOJI_REACTION,
|
|
|
|
// Don't resend using datasync, that would create quite a lot
|
|
|
|
// of traffic if clicking too eagelry
|
|
|
|
ResendAutomatically: false,
|
2020-07-22 00:38:01 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-07-22 00:57:44 +00:00
|
|
|
// Update MessengerResponse
|
2020-07-22 00:38:01 +00:00
|
|
|
response := MessengerResponse{}
|
2020-07-27 12:27:48 +00:00
|
|
|
emojiR.Retracted = true
|
|
|
|
response.EmojiReactions = []*EmojiReaction{emojiR}
|
2020-07-22 00:43:06 +00:00
|
|
|
response.Chats = []*Chat{chat}
|
2020-07-22 00:38:01 +00:00
|
|
|
|
2020-07-22 00:57:44 +00:00
|
|
|
// Persist retraction state for emoji reaction
|
2020-07-28 07:53:32 +00:00
|
|
|
err = m.persistence.SaveEmojiReaction(emojiR)
|
2020-07-22 00:43:06 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-07-22 00:38:01 +00:00
|
|
|
|
|
|
|
return &response, nil
|
2020-07-20 17:06:38 +00:00
|
|
|
}
|
2020-07-25 16:13:08 +00:00
|
|
|
|
2020-07-27 10:13:22 +00:00
|
|
|
func (m *Messenger) encodeChatEntity(chat *Chat, message common.ChatEntity) ([]byte, error) {
|
2020-07-25 16:13:08 +00:00
|
|
|
var encodedMessage []byte
|
|
|
|
var err error
|
2020-07-26 22:10:08 +00:00
|
|
|
l := m.logger.With(zap.String("site", "Send"), zap.String("chatID", chat.ID))
|
2020-07-25 16:13:08 +00:00
|
|
|
|
|
|
|
switch chat.ChatType {
|
|
|
|
case ChatTypeOneToOne:
|
2020-07-26 22:10:08 +00:00
|
|
|
l.Debug("sending private message")
|
2020-07-25 16:13:08 +00:00
|
|
|
message.SetMessageType(protobuf.MessageType_ONE_TO_ONE)
|
2020-07-26 22:06:58 +00:00
|
|
|
encodedMessage, err = proto.Marshal(message.GetProtobuf())
|
2020-07-25 16:13:08 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-10-20 15:10:28 +00:00
|
|
|
case ChatTypePublic, ChatTypeProfile:
|
2020-07-26 22:10:08 +00:00
|
|
|
l.Debug("sending public message", zap.String("chatName", chat.Name))
|
2020-07-25 16:13:08 +00:00
|
|
|
message.SetMessageType(protobuf.MessageType_PUBLIC_GROUP)
|
2020-07-26 22:06:58 +00:00
|
|
|
encodedMessage, err = proto.Marshal(message.GetProtobuf())
|
2020-07-25 16:13:08 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
case ChatTypePrivateGroupChat:
|
|
|
|
message.SetMessageType(protobuf.MessageType_PRIVATE_GROUP)
|
2020-07-26 22:10:08 +00:00
|
|
|
l.Debug("sending group message", zap.String("chatName", chat.Name))
|
2020-07-25 16:13:08 +00:00
|
|
|
|
|
|
|
group, err := newProtocolGroupFromChat(chat)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-07-25 21:25:40 +00:00
|
|
|
encodedMessage, err = m.processor.EncodeMembershipUpdate(group, message)
|
2020-07-25 16:13:08 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
return nil, errors.New("chat type not supported")
|
|
|
|
}
|
|
|
|
|
|
|
|
return encodedMessage, nil
|
|
|
|
}
|