2019-07-08 09:21:21 +00:00
|
|
|
package statusproto
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"crypto/ecdsa"
|
2019-07-17 08:45:14 +00:00
|
|
|
"fmt"
|
2019-07-16 10:43:07 +00:00
|
|
|
"path/filepath"
|
2019-07-08 09:21:21 +00:00
|
|
|
"time"
|
|
|
|
|
2019-07-18 13:41:48 +00:00
|
|
|
"go.uber.org/zap"
|
|
|
|
|
2019-07-08 09:21:21 +00:00
|
|
|
"github.com/pkg/errors"
|
2019-07-16 10:43:07 +00:00
|
|
|
whisper "github.com/status-im/whisper/whisperv6"
|
|
|
|
|
2019-07-17 08:45:14 +00:00
|
|
|
"github.com/status-im/status-protocol-go/datasync"
|
|
|
|
datasyncpeer "github.com/status-im/status-protocol-go/datasync/peer"
|
2019-07-16 10:43:07 +00:00
|
|
|
"github.com/status-im/status-protocol-go/encryption"
|
|
|
|
"github.com/status-im/status-protocol-go/encryption/multidevice"
|
|
|
|
"github.com/status-im/status-protocol-go/encryption/sharedsecret"
|
|
|
|
migrations "github.com/status-im/status-protocol-go/internal/sqlite"
|
|
|
|
"github.com/status-im/status-protocol-go/sqlite"
|
|
|
|
transport "github.com/status-im/status-protocol-go/transport/whisper"
|
|
|
|
"github.com/status-im/status-protocol-go/transport/whisper/filter"
|
2019-07-08 09:21:21 +00:00
|
|
|
protocol "github.com/status-im/status-protocol-go/v1"
|
2019-07-17 08:45:14 +00:00
|
|
|
datasyncnode "github.com/vacp2p/mvds/node"
|
|
|
|
datasyncpeers "github.com/vacp2p/mvds/peers"
|
|
|
|
datasyncstate "github.com/vacp2p/mvds/state"
|
|
|
|
datasyncstore "github.com/vacp2p/mvds/store"
|
2019-07-23 08:33:57 +00:00
|
|
|
)
|
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
var (
|
|
|
|
ErrChatIDEmpty = errors.New("chat ID is empty")
|
|
|
|
ErrNotImplemented = errors.New("not implemented")
|
|
|
|
)
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-16 10:43:07 +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 {
|
|
|
|
identity *ecdsa.PrivateKey
|
|
|
|
persistence persistence
|
|
|
|
adapter *whisperAdapter
|
|
|
|
encryptor *encryption.Protocol
|
2019-07-26 06:19:03 +00:00
|
|
|
logger *zap.Logger
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-23 08:33:57 +00:00
|
|
|
ownMessages map[string][]*protocol.Message
|
|
|
|
featureFlags featureFlags
|
|
|
|
messagesPersistenceEnabled bool
|
2019-07-26 06:19:03 +00:00
|
|
|
shutdownTasks []func() error
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
|
|
|
|
2019-07-17 14:50:09 +00:00
|
|
|
type featureFlags struct {
|
|
|
|
genericDiscoveryTopicEnabled bool
|
2019-07-16 15:16:35 +00:00
|
|
|
// sendV1Messages indicates whether we should send
|
|
|
|
// messages compatible only with V1 and later.
|
|
|
|
// V1 messages adds additional wrapping
|
|
|
|
// which contains a signature and payload.
|
|
|
|
sendV1Messages bool
|
2019-07-17 08:45:14 +00:00
|
|
|
|
|
|
|
// datasync indicates whether messages should be sent using datasync, breaking change for non-v1 clients
|
|
|
|
datasync bool
|
2019-07-17 14:50:09 +00:00
|
|
|
}
|
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
type config struct {
|
|
|
|
onNewInstallationsHandler func([]*multidevice.Installation)
|
2019-07-23 08:33:57 +00:00
|
|
|
// DEPRECATED: no need to expose it
|
|
|
|
onNewSharedSecretHandler func([]*sharedsecret.Secret)
|
|
|
|
// DEPRECATED: no need to expose it
|
|
|
|
onSendContactCodeHandler func(*encryption.ProtocolMessageSpec)
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-23 08:33:57 +00:00
|
|
|
encryptionLayerFilePath string
|
|
|
|
transportLayerFilePath string
|
2019-07-17 14:50:09 +00:00
|
|
|
|
2019-07-23 08:33:57 +00:00
|
|
|
messagesPersistenceEnabled bool
|
|
|
|
featureFlags featureFlags
|
2019-07-18 13:41:48 +00:00
|
|
|
|
|
|
|
logger *zap.Logger
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
type Option func(*config) error
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
func WithOnNewInstallationsHandler(h func([]*multidevice.Installation)) func(c *config) error {
|
|
|
|
return func(c *config) error {
|
|
|
|
c.onNewInstallationsHandler = h
|
|
|
|
return nil
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
2019-07-16 10:43:07 +00:00
|
|
|
}
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
func WithOnNewSharedSecret(h func([]*sharedsecret.Secret)) func(c *config) error {
|
|
|
|
return func(c *config) error {
|
|
|
|
c.onNewSharedSecretHandler = h
|
|
|
|
return nil
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-23 08:33:57 +00:00
|
|
|
func WithCustomLogger(logger *zap.Logger) func(c *config) error {
|
2019-07-16 10:43:07 +00:00
|
|
|
return func(c *config) error {
|
2019-07-23 08:33:57 +00:00
|
|
|
c.logger = logger
|
2019-07-16 10:43:07 +00:00
|
|
|
return nil
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-23 08:33:57 +00:00
|
|
|
func WithGenericDiscoveryTopicSupport() func(c *config) error {
|
2019-07-18 13:41:48 +00:00
|
|
|
return func(c *config) error {
|
2019-07-23 08:33:57 +00:00
|
|
|
c.featureFlags.genericDiscoveryTopicEnabled = true
|
2019-07-18 13:41:48 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-23 08:33:57 +00:00
|
|
|
func WithMessagesPersistenceEnabled() func(c *config) error {
|
2019-07-17 14:50:09 +00:00
|
|
|
return func(c *config) error {
|
2019-07-23 08:33:57 +00:00
|
|
|
c.messagesPersistenceEnabled = true
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: use this config fileds.
|
|
|
|
func WithDatabaseFilePaths(encryptionLayerFilePath, transportLayerFilePath string) func(c *config) error {
|
|
|
|
return func(c *config) error {
|
|
|
|
c.encryptionLayerFilePath = encryptionLayerFilePath
|
|
|
|
c.transportLayerFilePath = transportLayerFilePath
|
2019-07-17 14:50:09 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-16 15:16:35 +00:00
|
|
|
func WithSendV1Messages() func(c *config) error {
|
|
|
|
return func(c *config) error {
|
|
|
|
c.featureFlags.sendV1Messages = true
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-17 08:45:14 +00:00
|
|
|
func WithDatasync() func(c *config) error {
|
|
|
|
return func(c *config) error {
|
|
|
|
c.featureFlags.datasync = true
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
func NewMessenger(
|
|
|
|
identity *ecdsa.PrivateKey,
|
|
|
|
server transport.Server,
|
|
|
|
shh *whisper.Whisper,
|
|
|
|
dataDir string,
|
|
|
|
dbKey string,
|
|
|
|
installationID string,
|
|
|
|
opts ...Option,
|
|
|
|
) (*Messenger, error) {
|
|
|
|
var messenger *Messenger
|
|
|
|
|
2019-07-18 13:41:48 +00:00
|
|
|
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-07-16 10:43:07 +00:00
|
|
|
// Set default config fields.
|
2019-07-18 13:41:48 +00:00
|
|
|
if c.onNewInstallationsHandler == nil {
|
|
|
|
c.onNewInstallationsHandler = func(installations []*multidevice.Installation) {
|
|
|
|
sugar := logger.Sugar().With("site", "onNewInstallationsHandler")
|
2019-07-16 10:43:07 +00:00
|
|
|
for _, installation := range installations {
|
2019-07-18 13:41:48 +00:00
|
|
|
sugar.Infow(
|
|
|
|
"received a new installation",
|
|
|
|
"identity", installation.Identity,
|
|
|
|
"id", installation.ID)
|
2019-07-16 10:43:07 +00:00
|
|
|
}
|
2019-07-18 13:41:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if c.onNewSharedSecretHandler == nil {
|
|
|
|
c.onNewSharedSecretHandler = func(secrets []*sharedsecret.Secret) {
|
2019-07-16 10:43:07 +00:00
|
|
|
if err := messenger.handleSharedSecrets(secrets); err != nil {
|
2019-07-18 13:41:48 +00:00
|
|
|
slogger := logger.With(zap.String("site", "onNewSharedSecretHandler"))
|
|
|
|
slogger.Warn("failed to process secrets", zap.Error(err))
|
2019-07-16 10:43:07 +00:00
|
|
|
}
|
2019-07-18 13:41:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if c.onSendContactCodeHandler == nil {
|
|
|
|
c.onSendContactCodeHandler = func(messageSpec *encryption.ProtocolMessageSpec) {
|
|
|
|
slogger := logger.With(zap.String("site", "onSendContactCodeHandler"))
|
|
|
|
slogger.Info("received a SendContactCode request")
|
2019-07-16 10:43:07 +00:00
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
|
|
defer cancel()
|
|
|
|
_, err := messenger.adapter.SendContactCode(ctx, messageSpec)
|
2019-07-23 18:38:50 +00:00
|
|
|
if err != nil {
|
|
|
|
slogger.Warn("failed to send a contact code", zap.Error(err))
|
|
|
|
}
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
2019-07-16 10:43:07 +00:00
|
|
|
}
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-23 08:33:57 +00:00
|
|
|
// Set default database file paths.
|
|
|
|
if c.encryptionLayerFilePath == "" {
|
|
|
|
c.encryptionLayerFilePath = filepath.Join(dataDir, "sessions.sql")
|
|
|
|
}
|
|
|
|
if c.transportLayerFilePath == "" {
|
|
|
|
c.transportLayerFilePath = filepath.Join(dataDir, "transport.sql")
|
|
|
|
}
|
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
t, err := transport.NewWhisperServiceTransport(
|
|
|
|
server,
|
|
|
|
shh,
|
|
|
|
identity,
|
2019-07-23 08:33:57 +00:00
|
|
|
c.transportLayerFilePath,
|
2019-07-16 10:43:07 +00:00
|
|
|
dbKey,
|
|
|
|
nil,
|
2019-07-18 13:41:48 +00:00
|
|
|
logger,
|
2019-07-16 10:43:07 +00:00
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "failed to create a WhisperServiceTransport")
|
|
|
|
}
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
encryptionProtocol, err := encryption.New(
|
2019-07-23 08:33:57 +00:00
|
|
|
c.encryptionLayerFilePath,
|
2019-07-16 10:43:07 +00:00
|
|
|
dbKey,
|
|
|
|
installationID,
|
|
|
|
c.onNewInstallationsHandler,
|
|
|
|
c.onNewSharedSecretHandler,
|
|
|
|
c.onSendContactCodeHandler,
|
2019-07-18 13:41:48 +00:00
|
|
|
logger,
|
2019-07-16 10:43:07 +00:00
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "failed to create the encryption layer")
|
|
|
|
}
|
|
|
|
|
2019-07-17 08:45:14 +00:00
|
|
|
messagesDatabaseFileName := fmt.Sprintf("%s.messages.sql", installationID)
|
|
|
|
|
2019-07-23 08:33:57 +00:00
|
|
|
applicationLayerFilePath := filepath.Join(dataDir, messagesDatabaseFileName)
|
|
|
|
applicationLayerPersistence, err := sqlite.Open(applicationLayerFilePath, dbKey, sqlite.MigrationConfig{
|
2019-07-16 10:43:07 +00:00
|
|
|
AssetNames: migrations.AssetNames(),
|
|
|
|
AssetGetter: func(name string) ([]byte, error) {
|
|
|
|
return migrations.Asset(name)
|
|
|
|
},
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "failed to initialize messages db")
|
|
|
|
}
|
2019-07-17 08:45:14 +00:00
|
|
|
dataSyncTransport := datasync.NewDataSyncNodeTransport()
|
|
|
|
dataSyncStore := datasyncstore.NewDummyStore()
|
|
|
|
dataSyncNode := datasyncnode.NewNode(
|
|
|
|
&dataSyncStore,
|
|
|
|
dataSyncTransport,
|
|
|
|
datasyncstate.NewSyncState(), // @todo sqlite syncstate
|
|
|
|
datasync.CalculateSendTime,
|
|
|
|
0,
|
|
|
|
datasyncpeer.PublicKeyToPeerID(identity.PublicKey),
|
|
|
|
datasyncnode.BATCH,
|
|
|
|
datasyncpeers.NewMemoryPersistence(),
|
|
|
|
)
|
|
|
|
datasync := &datasync.DataSync{
|
|
|
|
Node: dataSyncNode,
|
|
|
|
DataSyncNodeTransport: dataSyncTransport,
|
|
|
|
}
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-23 08:33:57 +00:00
|
|
|
persistence := &sqlitePersistence{db: applicationLayerPersistence}
|
2019-07-17 08:45:14 +00:00
|
|
|
adapter := newWhisperAdapter(identity, t, encryptionProtocol, datasync, c.featureFlags, logger)
|
2019-07-16 10:43:07 +00:00
|
|
|
messenger = &Messenger{
|
2019-07-23 08:33:57 +00:00
|
|
|
identity: identity,
|
|
|
|
persistence: persistence,
|
|
|
|
adapter: adapter,
|
|
|
|
encryptor: encryptionProtocol,
|
|
|
|
ownMessages: make(map[string][]*protocol.Message),
|
|
|
|
featureFlags: c.featureFlags,
|
|
|
|
messagesPersistenceEnabled: c.messagesPersistenceEnabled,
|
|
|
|
shutdownTasks: []func() error{
|
|
|
|
persistence.Close,
|
|
|
|
adapter.transport.Reset,
|
2019-07-17 08:45:14 +00:00
|
|
|
func() error { datasync.Node.Stop(); return nil },
|
2019-07-24 06:53:51 +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 },
|
2019-07-18 13:41:48 +00:00
|
|
|
},
|
2019-07-23 08:33:57 +00:00
|
|
|
logger: logger,
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
|
|
|
|
2019-07-17 13:14:16 +00:00
|
|
|
// Start all services immediately.
|
|
|
|
// TODO: consider removing identity as an argument to Start().
|
|
|
|
if err := encryptionProtocol.Start(identity); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-07-17 08:45:14 +00:00
|
|
|
if c.featureFlags.datasync {
|
|
|
|
dataSyncNode.Start(300 * time.Millisecond)
|
|
|
|
}
|
2019-07-17 13:14:16 +00:00
|
|
|
|
2019-07-23 08:33:57 +00:00
|
|
|
logger.Debug("messages persistence", zap.Bool("enabled", c.messagesPersistenceEnabled))
|
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
return messenger, nil
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
|
|
|
|
2019-07-18 13:41:48 +00:00
|
|
|
// Shutdown takes care of ensuring a clean shutdown of Messenger
|
2019-07-23 08:33:57 +00:00
|
|
|
func (m *Messenger) Shutdown() (err error) {
|
2019-07-18 13:41:48 +00:00
|
|
|
for _, task := range m.shutdownTasks {
|
2019-07-23 08:33:57 +00:00
|
|
|
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())
|
|
|
|
}
|
|
|
|
}
|
2019-07-18 13:41:48 +00:00
|
|
|
}
|
2019-07-23 08:33:57 +00:00
|
|
|
return
|
2019-07-18 13:41:48 +00:00
|
|
|
}
|
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
func (m *Messenger) handleSharedSecrets(secrets []*sharedsecret.Secret) error {
|
|
|
|
return m.adapter.handleSharedSecrets(secrets)
|
|
|
|
}
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
func (m *Messenger) EnableInstallation(id string) error {
|
|
|
|
return m.encryptor.EnableInstallation(&m.identity.PublicKey, id)
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
func (m *Messenger) DisableInstallation(id string) error {
|
|
|
|
return m.encryptor.DisableInstallation(&m.identity.PublicKey, id)
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
func (m *Messenger) Installations() ([]*multidevice.Installation, error) {
|
|
|
|
return m.encryptor.GetOurInstallations(&m.identity.PublicKey)
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
func (m *Messenger) SetInstallationMetadata(id string, data *multidevice.InstallationMetadata) error {
|
|
|
|
return m.encryptor.SetInstallationMetadata(&m.identity.PublicKey, id, data)
|
|
|
|
}
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-23 08:33:57 +00:00
|
|
|
// NOT IMPLEMENTED
|
2019-07-16 10:43:07 +00:00
|
|
|
func (m *Messenger) SelectMailserver(id string) error {
|
|
|
|
return ErrNotImplemented
|
|
|
|
}
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-23 08:33:57 +00:00
|
|
|
// NOT IMPLEMENTED
|
2019-07-16 10:43:07 +00:00
|
|
|
func (m *Messenger) AddMailserver(enode string) error {
|
|
|
|
return ErrNotImplemented
|
|
|
|
}
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-23 08:33:57 +00:00
|
|
|
// NOT IMPLEMENTED
|
2019-07-16 10:43:07 +00:00
|
|
|
func (m *Messenger) RemoveMailserver(id string) error {
|
|
|
|
return ErrNotImplemented
|
|
|
|
}
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-23 08:33:57 +00:00
|
|
|
// NOT IMPLEMENTED
|
2019-07-16 10:43:07 +00:00
|
|
|
func (m *Messenger) Mailservers() ([]string, error) {
|
|
|
|
return nil, ErrNotImplemented
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
func (m *Messenger) Join(chat Chat) error {
|
|
|
|
if chat.PublicKey() != nil {
|
|
|
|
return m.adapter.JoinPrivate(chat.PublicKey())
|
|
|
|
} else if chat.PublicName() != "" {
|
|
|
|
return m.adapter.JoinPublic(chat.PublicName())
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
2019-07-16 10:43:07 +00:00
|
|
|
return errors.New("chat is neither public nor private")
|
|
|
|
}
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
func (m *Messenger) Leave(chat Chat) error {
|
|
|
|
if chat.PublicKey() != nil {
|
|
|
|
return m.adapter.LeavePrivate(chat.PublicKey())
|
|
|
|
} else if chat.PublicName() != "" {
|
|
|
|
return m.adapter.LeavePublic(chat.PublicName())
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
2019-07-16 10:43:07 +00:00
|
|
|
return errors.New("chat is neither public nor private")
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
func (m *Messenger) Send(ctx context.Context, chat Chat, data []byte) ([]byte, error) {
|
|
|
|
chatID := chat.ID()
|
|
|
|
if chatID == "" {
|
|
|
|
return nil, ErrChatIDEmpty
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
clock, err := m.persistence.LastMessageClock(chat.ID())
|
2019-07-08 09:21:21 +00:00
|
|
|
if err != nil {
|
2019-07-16 10:43:07 +00:00
|
|
|
return nil, err
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
if chat.PublicKey() != nil {
|
|
|
|
hash, message, err := m.adapter.SendPrivate(ctx, chat.PublicKey(), chat.ID(), data, clock)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
// Save our message because it won't be received from the transport layer.
|
|
|
|
message.ID = hash // a Message need ID to be properly stored in the db
|
|
|
|
message.SigPubKey = &m.identity.PublicKey
|
2019-07-23 08:33:57 +00:00
|
|
|
|
|
|
|
if m.messagesPersistenceEnabled {
|
|
|
|
_, err = m.persistence.SaveMessages(chat.ID(), []*protocol.Message{message})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-07-16 10:43:07 +00:00
|
|
|
}
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
// Cache it to be returned in Retrieve().
|
|
|
|
m.ownMessages[chatID] = append(m.ownMessages[chatID], message)
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
return hash, nil
|
|
|
|
} else if chat.PublicName() != "" {
|
|
|
|
return m.adapter.SendPublic(ctx, chat.PublicName(), chat.ID(), data, clock)
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
2019-07-16 10:43:07 +00:00
|
|
|
return nil, errors.New("chat is neither public nor private")
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
|
|
|
|
2019-07-23 08:33:57 +00:00
|
|
|
// SendRaw takes encoded data, encrypts it and sends through the wire.
|
|
|
|
// DEPRECATED
|
|
|
|
func (m *Messenger) SendRaw(ctx context.Context, chat Chat, data []byte) ([]byte, whisper.NewMessage, error) {
|
|
|
|
if chat.PublicKey() != nil {
|
|
|
|
return m.adapter.SendPrivateRaw(ctx, chat.PublicKey(), data)
|
|
|
|
} else if chat.PublicName() != "" {
|
|
|
|
return m.adapter.SendPublicRaw(ctx, chat.PublicName(), data)
|
|
|
|
}
|
|
|
|
return nil, whisper.NewMessage{}, errors.New("chat is neither public nor private")
|
|
|
|
}
|
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
type RetrieveConfig struct {
|
|
|
|
From time.Time
|
|
|
|
To time.Time
|
|
|
|
latest bool
|
|
|
|
last24Hours bool
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
var (
|
|
|
|
RetrieveLatest = RetrieveConfig{latest: true}
|
|
|
|
RetrieveLastDay = RetrieveConfig{latest: true, last24Hours: true}
|
|
|
|
)
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-26 06:19:03 +00:00
|
|
|
// RetrieveAll retrieves all previously fetched messages
|
|
|
|
func (m *Messenger) RetrieveAll(ctx context.Context, c RetrieveConfig) (allMessages []*protocol.Message, err error) {
|
|
|
|
latest, err := m.adapter.RetrieveAllMessages()
|
|
|
|
if err != nil {
|
|
|
|
err = errors.Wrap(err, "failed to retrieve messages")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, messages := range latest {
|
|
|
|
chatID := messages.ChatID
|
|
|
|
|
|
|
|
_, err = m.persistence.SaveMessages(chatID, messages.Messages)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "failed to save messages")
|
|
|
|
}
|
|
|
|
|
|
|
|
if !messages.Public {
|
|
|
|
// Return any own messages for this chat as well.
|
|
|
|
if ownMessages, ok := m.ownMessages[chatID]; ok {
|
|
|
|
messages.Messages = append(messages.Messages, ownMessages...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
retrievedMessages, err := m.retrieveSaved(ctx, chatID, c, messages.Messages)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "failed to get saved messages")
|
|
|
|
}
|
|
|
|
|
|
|
|
allMessages = append(allMessages, retrievedMessages...)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete own messages as they were added to the result.
|
|
|
|
for _, messages := range latest {
|
|
|
|
if !messages.Public {
|
|
|
|
delete(m.ownMessages, messages.ChatID)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
func (m *Messenger) Retrieve(ctx context.Context, chat Chat, c RetrieveConfig) (messages []*protocol.Message, err error) {
|
2019-07-23 08:33:57 +00:00
|
|
|
var (
|
|
|
|
latest []*protocol.Message
|
|
|
|
ownLatest []*protocol.Message
|
|
|
|
)
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
if chat.PublicKey() != nil {
|
|
|
|
latest, err = m.adapter.RetrievePrivateMessages(chat.PublicKey())
|
|
|
|
// Return any own messages for this chat as well.
|
|
|
|
if ownMessages, ok := m.ownMessages[chat.ID()]; ok {
|
2019-07-23 08:33:57 +00:00
|
|
|
ownLatest = ownMessages
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
2019-07-16 10:43:07 +00:00
|
|
|
} else if chat.PublicName() != "" {
|
|
|
|
latest, err = m.adapter.RetrievePublicMessages(chat.PublicName())
|
|
|
|
} else {
|
|
|
|
return nil, errors.New("chat is neither public nor private")
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
if err != nil {
|
|
|
|
err = errors.Wrap(err, "failed to retrieve messages")
|
|
|
|
return
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
|
|
|
|
2019-07-23 08:33:57 +00:00
|
|
|
if m.messagesPersistenceEnabled {
|
|
|
|
_, err = m.persistence.SaveMessages(chat.ID(), latest)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "failed to save latest messages")
|
|
|
|
}
|
2019-07-16 10:43:07 +00:00
|
|
|
}
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-23 08:33:57 +00:00
|
|
|
// Confirm received and decrypted messages.
|
|
|
|
if m.messagesPersistenceEnabled && chat.PublicKey() != nil {
|
|
|
|
for _, message := range latest {
|
|
|
|
// Confirm received and decrypted messages.
|
|
|
|
if err := m.encryptor.ConfirmMessageProcessed(message.ID); err != nil {
|
|
|
|
return nil, errors.Wrap(err, "failed to confirm message being processed")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-26 06:19:03 +00:00
|
|
|
// We may need to add more messages from the past.
|
|
|
|
result, err := m.retrieveSaved(ctx, chat.ID(), c, append(latest, ownLatest...))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-07-23 08:33:57 +00:00
|
|
|
// When our messages are returned, we can delete them.
|
|
|
|
delete(m.ownMessages, chat.ID())
|
|
|
|
|
2019-07-26 06:19:03 +00:00
|
|
|
return result, nil
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
|
|
|
|
2019-07-26 06:19:03 +00:00
|
|
|
func (m *Messenger) retrieveSaved(ctx context.Context, chatID string, c RetrieveConfig, latest []*protocol.Message) (messages []*protocol.Message, err error) {
|
2019-07-23 08:33:57 +00:00
|
|
|
if !m.messagesPersistenceEnabled {
|
|
|
|
return latest, nil
|
|
|
|
}
|
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
if !c.latest {
|
2019-07-26 06:19:03 +00:00
|
|
|
return m.persistence.Messages(chatID, c.From, c.To)
|
2019-07-16 10:43:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if c.last24Hours {
|
|
|
|
to := time.Now()
|
|
|
|
from := to.Add(-time.Hour * 24)
|
2019-07-26 06:19:03 +00:00
|
|
|
return m.persistence.Messages(chatID, from, to)
|
2019-07-16 10:43:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return latest, nil
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
2019-07-23 08:33:57 +00:00
|
|
|
|
|
|
|
// DEPRECATED
|
|
|
|
func (m *Messenger) RetrieveRawAll() (map[filter.Chat][]*whisper.Message, error) {
|
|
|
|
return m.adapter.RetrieveRawAll()
|
|
|
|
}
|
|
|
|
|
|
|
|
// DEPRECATED
|
|
|
|
func (m *Messenger) RetrieveRawWithFilter(filterID string) ([]*whisper.Message, error) {
|
|
|
|
return m.adapter.RetrieveRaw(filterID)
|
|
|
|
}
|
|
|
|
|
|
|
|
// DEPRECATED
|
|
|
|
func (m *Messenger) LoadFilters(chats []*filter.Chat) ([]*filter.Chat, error) {
|
|
|
|
return m.adapter.transport.LoadFilters(chats, m.featureFlags.genericDiscoveryTopicEnabled)
|
|
|
|
}
|
|
|
|
|
|
|
|
// DEPRECATED
|
|
|
|
func (m *Messenger) RemoveFilters(chats []*filter.Chat) error {
|
|
|
|
return m.adapter.transport.RemoveFilters(chats)
|
|
|
|
}
|
|
|
|
|
|
|
|
// DEPRECATED
|
|
|
|
func (m *Messenger) ConfirmMessagesProcessed(messageIDs [][]byte) error {
|
|
|
|
for _, id := range messageIDs {
|
|
|
|
if err := m.encryptor.ConfirmMessageProcessed(id); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|