mirror of
https://github.com/status-im/status-go.git
synced 2025-02-24 12:48:46 +00:00
Add community notifications (#2160)
This commit introduces the following changes: - `local-notifications` require as body an interface complying with `json.Marshaler` - removed unmarshaling of `Notifications` as not used (we only Marshal notifications) - `protocol/messenger.go` creates directly a `Notification` instead of having an intermediate format - add community notifications on request to join - move parsing of text in status-go for notifications
This commit is contained in:
parent
5a76e93063
commit
c55659b4f6
@ -31,7 +31,6 @@ import (
|
||||
"github.com/status-im/status-go/params"
|
||||
"github.com/status-im/status-go/profiling"
|
||||
"github.com/status-im/status-go/protocol"
|
||||
localnotifications "github.com/status-im/status-go/services/local-notifications"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -400,13 +399,11 @@ func retrieveMessagesLoop(messenger *protocol.Messenger, tick time.Duration, can
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
mr, err := messenger.RetrieveAll()
|
||||
_, err := messenger.RetrieveAll()
|
||||
if err != nil {
|
||||
logger.Error("failed to retrieve raw messages", "err", err)
|
||||
continue
|
||||
}
|
||||
|
||||
localnotifications.SendMessageNotifications(mr.Notifications)
|
||||
case <-cancel:
|
||||
return
|
||||
}
|
||||
|
@ -530,6 +530,15 @@ func (db *Database) DeleteAccount(address types.Address) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *Database) GetNotificationsEnabled() (bool, error) {
|
||||
var result bool
|
||||
err := db.db.QueryRow("SELECT notifications_enabled FROM settings WHERE synthetic_id = 'id'").Scan(&result)
|
||||
if err == sql.ErrNoRows {
|
||||
return result, nil
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (db *Database) GetWalletAddress() (rst types.Address, err error) {
|
||||
err = db.db.QueryRow("SELECT address FROM accounts WHERE wallet = 1").Scan(&rst)
|
||||
return
|
||||
|
@ -108,6 +108,14 @@ func (c *Chat) OneToOne() bool {
|
||||
return c.ChatType == ChatTypeOneToOne
|
||||
}
|
||||
|
||||
func (c *Chat) CommunityChat() bool {
|
||||
return c.ChatType == ChatTypeCommunityChat
|
||||
}
|
||||
|
||||
func (c *Chat) PrivateGroupChat() bool {
|
||||
return c.ChatType == ChatTypePrivateGroupChat
|
||||
}
|
||||
|
||||
func (c *Chat) CommunityChatID() string {
|
||||
if c.ChatType != ChatTypeCommunityChat {
|
||||
return c.ID
|
||||
@ -291,6 +299,25 @@ func CreateCommunityChat(orgID, chatID string, orgChat *protobuf.CommunityChat,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Chat) DeepLink() string {
|
||||
if c.OneToOne() {
|
||||
return "status-im://p/" + c.ID
|
||||
}
|
||||
if c.PrivateGroupChat() {
|
||||
return "status-im://g/args?a2=" + c.ID
|
||||
}
|
||||
|
||||
if c.CommunityChat() {
|
||||
return "status-im://cc/" + c.ID
|
||||
}
|
||||
|
||||
if c.Public() {
|
||||
return "status-im://" + c.ID
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func CreateCommunityChats(org *communities.Community, timesource common.TimeSource) []*Chat {
|
||||
var chats []*Chat
|
||||
orgID := org.IDString()
|
||||
|
@ -130,6 +130,7 @@ func (s *ChatTestSuite) TestSerializeJSON() {
|
||||
message.Clock = 1
|
||||
message.Text = "`some markdown text`"
|
||||
s.Require().NoError(message.PrepareContent())
|
||||
message.ParsedTextAst = nil
|
||||
chat.LastMessage = message
|
||||
|
||||
encodedJSON, err := json.Marshal(chat)
|
||||
|
@ -110,6 +110,8 @@ type Message struct {
|
||||
RTL bool `json:"rtl"`
|
||||
// ParsedText is the parsed markdown for displaying
|
||||
ParsedText []byte `json:"parsedText,omitempty"`
|
||||
// ParsedTextAst is the ast of the parsed text
|
||||
ParsedTextAst *ast.Node `json:"-"`
|
||||
// LineCount is the count of newlines in the message
|
||||
LineCount int `json:"lineCount"`
|
||||
// Base64Image is the converted base64 image
|
||||
@ -323,12 +325,52 @@ func (m *Message) parseAudio() error {
|
||||
}
|
||||
|
||||
// implement interface of https://github.com/status-im/markdown/blob/b9fe921681227b1dace4b56364e15edb3b698308/ast/node.go#L701
|
||||
type NodeVisitor struct {
|
||||
type SimplifiedTextVisitor struct {
|
||||
text string
|
||||
canonicalNames map[string]string
|
||||
}
|
||||
|
||||
func (v *SimplifiedTextVisitor) Visit(node ast.Node, entering bool) ast.WalkStatus {
|
||||
// only on entering we fetch, otherwise we go on
|
||||
if !entering {
|
||||
return ast.GoToNext
|
||||
}
|
||||
|
||||
switch n := node.(type) {
|
||||
case *ast.Mention:
|
||||
literal := string(n.Literal)
|
||||
canonicalName, ok := v.canonicalNames[literal]
|
||||
if ok {
|
||||
v.text += canonicalName
|
||||
} else {
|
||||
v.text += literal
|
||||
}
|
||||
case *ast.Link:
|
||||
destination := string(n.Destination)
|
||||
v.text += destination
|
||||
default:
|
||||
var literal string
|
||||
|
||||
leaf := node.AsLeaf()
|
||||
container := node.AsContainer()
|
||||
if leaf != nil {
|
||||
literal = string(leaf.Literal)
|
||||
} else if container != nil {
|
||||
literal = string(container.Literal)
|
||||
}
|
||||
v.text += literal
|
||||
}
|
||||
|
||||
return ast.GoToNext
|
||||
}
|
||||
|
||||
// implement interface of https://github.com/status-im/markdown/blob/b9fe921681227b1dace4b56364e15edb3b698308/ast/node.go#L701
|
||||
type MentionsAndLinksVisitor struct {
|
||||
mentions []string
|
||||
links []string
|
||||
}
|
||||
|
||||
func (v *NodeVisitor) Visit(node ast.Node, entering bool) ast.WalkStatus {
|
||||
func (v *MentionsAndLinksVisitor) Visit(node ast.Node, entering bool) ast.WalkStatus {
|
||||
// only on entering we fetch, otherwise we go on
|
||||
if !entering {
|
||||
return ast.GoToNext
|
||||
@ -344,7 +386,7 @@ func (v *NodeVisitor) Visit(node ast.Node, entering bool) ast.WalkStatus {
|
||||
}
|
||||
|
||||
func extractMentionsAndLinks(parsedText ast.Node) ([]string, []string) {
|
||||
visitor := &NodeVisitor{}
|
||||
visitor := &MentionsAndLinksVisitor{}
|
||||
ast.Walk(parsedText, visitor)
|
||||
return visitor.mentions, visitor.links
|
||||
}
|
||||
@ -358,6 +400,7 @@ func (m *Message) PrepareContent() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.ParsedTextAst = &parsedText
|
||||
m.ParsedText = jsonParsedText
|
||||
m.LineCount = strings.Count(m.Text, "\n")
|
||||
m.RTL = isRTL(m.Text)
|
||||
@ -367,6 +410,34 @@ func (m *Message) PrepareContent() error {
|
||||
return m.parseAudio()
|
||||
}
|
||||
|
||||
// GetSimplifiedText returns a the text stripped of all the markdown and with mentions
|
||||
// replaced by canonical names
|
||||
func (m *Message) GetSimplifiedText(canonicalNames map[string]string) (string, error) {
|
||||
|
||||
if m.ContentType == protobuf.ChatMessage_AUDIO {
|
||||
return "Audio", nil
|
||||
}
|
||||
if m.ContentType == protobuf.ChatMessage_STICKER {
|
||||
return "Sticker", nil
|
||||
}
|
||||
if m.ContentType == protobuf.ChatMessage_IMAGE {
|
||||
return "Image", nil
|
||||
}
|
||||
if m.ContentType == protobuf.ChatMessage_COMMUNITY {
|
||||
return "Community", nil
|
||||
}
|
||||
|
||||
if m.ParsedTextAst == nil {
|
||||
err := m.PrepareContent()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
visitor := &SimplifiedTextVisitor{canonicalNames: canonicalNames}
|
||||
ast.Walk(*m.ParsedTextAst, visitor)
|
||||
return visitor.text, nil
|
||||
}
|
||||
|
||||
func getAudioMessageMIME(i *protobuf.AudioMessage) (string, error) {
|
||||
switch i.Type {
|
||||
case protobuf.AudioMessage_AAC:
|
||||
|
@ -98,3 +98,32 @@ func TestPrepareContentLinks(t *testing.T) {
|
||||
require.Equal(t, message.Links[0], link1)
|
||||
require.Equal(t, message.Links[1], link2)
|
||||
}
|
||||
|
||||
func TestPrepareSimplifiedText(t *testing.T) {
|
||||
canonicalName1 := "canonical-name-1"
|
||||
canonicalName2 := "canonical-name-2"
|
||||
|
||||
message := &Message{}
|
||||
pk1, err := crypto.GenerateKey()
|
||||
require.NoError(t, err)
|
||||
pk1String := types.EncodeHex(crypto.FromECDSAPub(&pk1.PublicKey))
|
||||
|
||||
pk2, err := crypto.GenerateKey()
|
||||
require.NoError(t, err)
|
||||
pk2String := types.EncodeHex(crypto.FromECDSAPub(&pk2.PublicKey))
|
||||
|
||||
message.Text = "hey @" + pk1String + " @" + pk2String
|
||||
|
||||
require.NoError(t, message.PrepareContent())
|
||||
require.Len(t, message.Mentions, 2)
|
||||
require.Equal(t, message.Mentions[0], pk1String)
|
||||
require.Equal(t, message.Mentions[1], pk2String)
|
||||
|
||||
canonicalNames := make(map[string]string)
|
||||
canonicalNames[pk1String] = canonicalName1
|
||||
canonicalNames[pk2String] = canonicalName2
|
||||
|
||||
simplifiedText, err := message.GetSimplifiedText(canonicalNames)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "hey "+canonicalName1+" "+canonicalName2, simplifiedText)
|
||||
}
|
||||
|
@ -116,7 +116,7 @@ func (o *Community) MarshalJSON() ([]byte, error) {
|
||||
communityItem.Members = o.config.CommunityDescription.Members
|
||||
communityItem.Permissions = o.config.CommunityDescription.Permissions
|
||||
if o.config.CommunityDescription.Identity != nil {
|
||||
communityItem.Name = o.config.CommunityDescription.Identity.DisplayName
|
||||
communityItem.Name = o.Name()
|
||||
communityItem.Color = o.config.CommunityDescription.Identity.Color
|
||||
communityItem.Description = o.config.CommunityDescription.Identity.Description
|
||||
for t, i := range o.config.CommunityDescription.Identity.Images {
|
||||
@ -132,6 +132,10 @@ func (o *Community) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(communityItem)
|
||||
}
|
||||
|
||||
func (o *Community) Name() string {
|
||||
return o.config.CommunityDescription.Identity.DisplayName
|
||||
}
|
||||
|
||||
func (o *Community) initialize() {
|
||||
if o.config.CommunityDescription == nil {
|
||||
o.config.CommunityDescription = &protobuf.CommunityDescription{}
|
||||
@ -339,19 +343,22 @@ func (o *Community) isBanned(pk *ecdsa.PublicKey) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (o *Community) hasPermission(pk *ecdsa.PublicKey, role protobuf.CommunityMember_Roles) bool {
|
||||
func (o *Community) hasMemberPermission(member *protobuf.CommunityMember, permissions map[protobuf.CommunityMember_Roles]bool) bool {
|
||||
for _, r := range member.Roles {
|
||||
if permissions[r] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (o *Community) hasPermission(pk *ecdsa.PublicKey, roles map[protobuf.CommunityMember_Roles]bool) bool {
|
||||
member := o.getMember(pk)
|
||||
if member == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, r := range member.Roles {
|
||||
if r == role {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return o.hasMemberPermission(member, roles)
|
||||
}
|
||||
|
||||
func (o *Community) HasMember(pk *ecdsa.PublicKey) bool {
|
||||
@ -605,6 +612,22 @@ func (o *Community) IsAdmin() bool {
|
||||
return o.config.PrivateKey != nil
|
||||
}
|
||||
|
||||
func (o *Community) IsMemberAdmin(publicKey *ecdsa.PublicKey) bool {
|
||||
return o.hasPermission(publicKey, adminRolePermissions())
|
||||
}
|
||||
|
||||
func canManageUsersRolePermissions() map[protobuf.CommunityMember_Roles]bool {
|
||||
roles := adminRolePermissions()
|
||||
roles[protobuf.CommunityMember_ROLE_MANAGE_USERS] = true
|
||||
return roles
|
||||
}
|
||||
|
||||
func adminRolePermissions() map[protobuf.CommunityMember_Roles]bool {
|
||||
roles := make(map[protobuf.CommunityMember_Roles]bool)
|
||||
roles[protobuf.CommunityMember_ROLE_ALL] = true
|
||||
return roles
|
||||
}
|
||||
|
||||
func (o *Community) validateRequestToJoinWithChatID(request *protobuf.CommunityRequestToJoin) error {
|
||||
|
||||
chat, ok := o.config.CommunityDescription.Chats[request.ChatId]
|
||||
@ -901,7 +924,8 @@ func (o *Community) CanManageUsers(pk *ecdsa.PublicKey) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
return o.hasPermission(pk, protobuf.CommunityMember_ROLE_ALL) || o.hasPermission(pk, protobuf.CommunityMember_ROLE_MANAGE_USERS)
|
||||
roles := canManageUsersRolePermissions()
|
||||
return o.hasPermission(pk, roles)
|
||||
|
||||
}
|
||||
func (o *Community) isMember() bool {
|
||||
@ -933,6 +957,23 @@ func (o *Community) nextClock() uint64 {
|
||||
return o.config.CommunityDescription.Clock + 1
|
||||
}
|
||||
|
||||
func (o *Community) CanManageUsersPublicKeys() ([]*ecdsa.PublicKey, error) {
|
||||
var response []*ecdsa.PublicKey
|
||||
roles := canManageUsersRolePermissions()
|
||||
for pkString, member := range o.config.CommunityDescription.Members {
|
||||
if o.hasMemberPermission(member, roles) {
|
||||
pk, err := common.HexToPubkey(pkString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response = append(response, pk)
|
||||
}
|
||||
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func emptyCommunityChanges() *CommunityChanges {
|
||||
return &CommunityChanges{
|
||||
MembersAdded: make(map[string]*protobuf.CommunityMember),
|
||||
|
@ -309,6 +309,8 @@ func (s *MessengerCommunitiesSuite) TestInviteUsersToCommunity() {
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(response)
|
||||
s.Require().Len(response.Communities(), 1)
|
||||
s.Require().True(response.Communities()[0].HasMember(&s.bob.identity.PublicKey))
|
||||
s.Require().True(response.Communities()[0].IsMemberAdmin(&s.bob.identity.PublicKey))
|
||||
|
||||
community := response.Communities()[0]
|
||||
|
||||
@ -787,6 +789,12 @@ func (s *MessengerCommunitiesSuite) TestRequestAccessAgain() {
|
||||
|
||||
s.Require().Equal(requestToJoin1.ID, requestToJoin2.ID)
|
||||
|
||||
// Check that a notification is been added to messenger
|
||||
|
||||
notifications := response.Notifications()
|
||||
s.Require().Len(notifications, 1)
|
||||
s.Require().NotEqual(notifications[0].ID.Hex(), "0x0000000000000000000000000000000000000000000000000000000000000000")
|
||||
|
||||
// Accept request
|
||||
|
||||
acceptRequestToJoin := &requests.AcceptRequestToJoinCommunity{ID: requestToJoin1.ID}
|
||||
|
@ -26,6 +26,22 @@ type ContactDeviceInfo struct {
|
||||
FCMToken string `json:"fcmToken"`
|
||||
}
|
||||
|
||||
func (c *Contact) CanonicalName() string {
|
||||
if c.LocalNickname != "" {
|
||||
return c.LocalNickname
|
||||
}
|
||||
|
||||
if c.ENSVerified {
|
||||
return c.Name
|
||||
}
|
||||
|
||||
return c.Alias
|
||||
}
|
||||
|
||||
func (c *Contact) CanonicalImage() string {
|
||||
return c.Identicon
|
||||
}
|
||||
|
||||
// Contact has information about a "Contact". A contact is not necessarily one
|
||||
// that we added or added us, that's based on SystemTags.
|
||||
type Contact struct {
|
||||
|
@ -1,9 +1,128 @@
|
||||
package protocol
|
||||
|
||||
import "github.com/status-im/status-go/protocol/common"
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"encoding/json"
|
||||
|
||||
type MessageNotificationBody struct {
|
||||
Message *common.Message `json:"message"`
|
||||
Contact *Contact `json:"contact"`
|
||||
Chat *Chat `json:"chat"`
|
||||
gethcommon "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/status-im/status-go/protocol/common"
|
||||
"github.com/status-im/status-go/protocol/communities"
|
||||
localnotifications "github.com/status-im/status-go/services/local-notifications"
|
||||
)
|
||||
|
||||
type NotificationBody struct {
|
||||
Message *common.Message `json:"message"`
|
||||
Contact *Contact `json:"contact"`
|
||||
Chat *Chat `json:"chat"`
|
||||
Community *communities.Community `json:"community"`
|
||||
}
|
||||
|
||||
func showMessageNotification(publicKey ecdsa.PublicKey, message *common.Message, chat *Chat, responseTo *common.Message) bool {
|
||||
if chat != nil && (chat.OneToOne() || chat.PrivateGroupChat()) {
|
||||
return true
|
||||
}
|
||||
|
||||
publicKeyString := common.PubkeyToHex(&publicKey)
|
||||
mentioned := false
|
||||
for _, mention := range message.Mentions {
|
||||
if publicKeyString == mention {
|
||||
mentioned = true
|
||||
}
|
||||
}
|
||||
|
||||
if mentioned {
|
||||
return true
|
||||
}
|
||||
|
||||
if responseTo != nil {
|
||||
return responseTo.From == publicKeyString
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (n NotificationBody) MarshalJSON() ([]byte, error) {
|
||||
type Alias NotificationBody
|
||||
item := struct{ *Alias }{Alias: (*Alias)(&n)}
|
||||
return json.Marshal(item)
|
||||
}
|
||||
|
||||
func NewMessageNotification(id string, message *common.Message, chat *Chat, contact *Contact, contacts map[string]*Contact) (*localnotifications.Notification, error) {
|
||||
body := &NotificationBody{
|
||||
Message: message,
|
||||
Chat: chat,
|
||||
Contact: contact,
|
||||
}
|
||||
|
||||
return body.toMessageNotification(id, contacts)
|
||||
}
|
||||
|
||||
func NewCommunityRequestToJoinNotification(id string, community *communities.Community, contact *Contact) *localnotifications.Notification {
|
||||
body := &NotificationBody{
|
||||
Community: community,
|
||||
Contact: contact,
|
||||
}
|
||||
|
||||
return body.toCommunityRequestToJoinNotification(id)
|
||||
}
|
||||
|
||||
func (n NotificationBody) toMessageNotification(id string, contacts map[string]*Contact) (*localnotifications.Notification, error) {
|
||||
var title string
|
||||
if n.Chat.PrivateGroupChat() || n.Chat.Public() || n.Chat.CommunityChat() {
|
||||
title = n.Chat.Name
|
||||
} else if n.Chat.OneToOne() {
|
||||
title = n.Contact.CanonicalName()
|
||||
|
||||
}
|
||||
|
||||
canonicalNames := make(map[string]string)
|
||||
for _, id := range n.Message.Mentions {
|
||||
contact, ok := contacts[id]
|
||||
if !ok {
|
||||
var err error
|
||||
contact, err = buildContactFromPkString(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
canonicalNames[id] = contact.CanonicalName()
|
||||
}
|
||||
|
||||
simplifiedText, err := n.Message.GetSimplifiedText(canonicalNames)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &localnotifications.Notification{
|
||||
Body: n,
|
||||
ID: gethcommon.HexToHash(id),
|
||||
BodyType: localnotifications.TypeMessage,
|
||||
Category: localnotifications.CategoryMessage,
|
||||
Deeplink: n.Chat.DeepLink(),
|
||||
Title: title,
|
||||
Message: simplifiedText,
|
||||
IsConversation: true,
|
||||
IsGroupConversation: true,
|
||||
Author: localnotifications.NotificationAuthor{
|
||||
Name: n.Contact.CanonicalName(),
|
||||
Icon: n.Contact.CanonicalImage(),
|
||||
ID: n.Contact.ID,
|
||||
},
|
||||
Timestamp: n.Message.WhisperTimestamp,
|
||||
ConversationID: n.Chat.ID,
|
||||
Image: "",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (n NotificationBody) toCommunityRequestToJoinNotification(id string) *localnotifications.Notification {
|
||||
return &localnotifications.Notification{
|
||||
ID: gethcommon.HexToHash(id),
|
||||
Body: n,
|
||||
Title: n.Contact.CanonicalName() + " wants to join " + n.Community.Name(),
|
||||
Message: n.Contact.CanonicalName() + " wants to join message " + n.Community.Name(),
|
||||
BodyType: localnotifications.TypeMessage,
|
||||
Category: localnotifications.CategoryCommunityRequestToJoin,
|
||||
Deeplink: "status-im://cr/" + n.Community.IDString(),
|
||||
Image: "",
|
||||
}
|
||||
}
|
||||
|
@ -415,6 +415,17 @@ func (m *MessageHandler) HandleCommunityRequestToJoin(state *ReceivedMessageStat
|
||||
|
||||
state.Response.RequestsToJoinCommunity = append(state.Response.RequestsToJoinCommunity, requestToJoin)
|
||||
|
||||
community, err := m.communitiesManager.GetByID(requestToJoinProto.CommunityId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
contactID := contactIDFromPublicKey(signer)
|
||||
|
||||
contact := state.AllContacts[contactID]
|
||||
|
||||
state.Response.AddNotification(NewCommunityRequestToJoinNotification(requestToJoin.ID.String(), community, contact))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -21,10 +21,12 @@ import (
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/golang/protobuf/proto"
|
||||
|
||||
"github.com/status-im/status-go/appdatabase"
|
||||
"github.com/status-im/status-go/eth-node/crypto"
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
userimage "github.com/status-im/status-go/images"
|
||||
"github.com/status-im/status-go/multiaccounts"
|
||||
"github.com/status-im/status-go/multiaccounts/accounts"
|
||||
"github.com/status-im/status-go/protocol/audio"
|
||||
"github.com/status-im/status-go/protocol/common"
|
||||
"github.com/status-im/status-go/protocol/communities"
|
||||
@ -99,6 +101,7 @@ type Messenger struct {
|
||||
mailserver []byte
|
||||
database *sql.DB
|
||||
multiAccounts *multiaccounts.Database
|
||||
settings *accounts.Database
|
||||
account *multiaccounts.Account
|
||||
mailserversDatabase *mailservers.Database
|
||||
quit chan struct{}
|
||||
@ -186,7 +189,7 @@ func NewMessenger(
|
||||
if c.db == nil {
|
||||
logger.Info("opening a database", zap.String("dbPath", c.dbConfig.dbPath))
|
||||
var err error
|
||||
database, err = sqlite.Open(c.dbConfig.dbPath, c.dbConfig.dbKey)
|
||||
database, err = appdatabase.InitializeDB(c.dbConfig.dbPath, c.dbConfig.dbKey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to initialize database from the db config")
|
||||
}
|
||||
@ -304,6 +307,7 @@ func NewMessenger(
|
||||
verifyTransactionClient: c.verifyTransactionClient,
|
||||
database: database,
|
||||
multiAccounts: c.multiAccount,
|
||||
settings: accounts.NewDB(database),
|
||||
mailserversDatabase: c.mailserversDatabase,
|
||||
account: c.account,
|
||||
quit: make(chan struct{}),
|
||||
@ -2455,7 +2459,7 @@ func (m *Messenger) markDeliveredMessages(acks [][]byte) {
|
||||
}
|
||||
}
|
||||
|
||||
// addNewMessageNotification takes a common.Message and generates a new MessageNotificationBody and appends it to the
|
||||
// addNewMessageNotification takes a common.Message and generates a new NotificationBody and appends it to the
|
||||
// []Response.Notifications if the message is m.New
|
||||
func (r *ReceivedMessageState) addNewMessageNotification(publicKey ecdsa.PublicKey, m *common.Message, responseTo *common.Message) error {
|
||||
if !m.New {
|
||||
@ -2469,17 +2473,14 @@ func (r *ReceivedMessageState) addNewMessageNotification(publicKey ecdsa.PublicK
|
||||
contactID := contactIDFromPublicKey(pubKey)
|
||||
|
||||
chat := r.AllChats[m.LocalChatID]
|
||||
notification := MessageNotificationBody{
|
||||
Message: m,
|
||||
Contact: r.AllContacts[contactID],
|
||||
Chat: chat,
|
||||
}
|
||||
contact := r.AllContacts[contactID]
|
||||
|
||||
if showNotification(publicKey, notification, responseTo) {
|
||||
r.Response.Notifications = append(
|
||||
r.Response.Notifications,
|
||||
notification,
|
||||
)
|
||||
if showMessageNotification(publicKey, m, chat, responseTo) {
|
||||
notification, err := NewMessageNotification(m.ID, m, chat, contact, r.AllContacts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.Response.AddNotification(notification)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -3009,13 +3010,19 @@ func (m *Messenger) handleRetrievedMessages(chatWithMessages map[transport.Filte
|
||||
}
|
||||
messageState.Response.Messages = messagesWithResponses
|
||||
|
||||
notificationsEnabled, err := m.settings.GetNotificationsEnabled()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, message := range messageState.Response.Messages {
|
||||
if _, ok := newMessagesIds[message.ID]; ok {
|
||||
message.New = true
|
||||
|
||||
// Create notification body to be eventually passed to `localnotifications.SendMessageNotifications()`
|
||||
if err = messageState.addNewMessageNotification(m.identity.PublicKey, message, messagesByID[message.ResponseTo]); err != nil {
|
||||
return nil, err
|
||||
if notificationsEnabled {
|
||||
// Create notification body to be eventually passed to `localnotifications.SendMessageNotifications()`
|
||||
if err = messageState.addNewMessageNotification(m.identity.PublicKey, message, messagesByID[message.ResponseTo]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3026,30 +3033,6 @@ func (m *Messenger) handleRetrievedMessages(chatWithMessages map[transport.Filte
|
||||
return messageState.Response, nil
|
||||
}
|
||||
|
||||
func showNotification(publicKey ecdsa.PublicKey, n MessageNotificationBody, responseTo *common.Message) bool {
|
||||
if n.Chat != nil && n.Chat.ChatType == ChatTypeOneToOne {
|
||||
return true
|
||||
}
|
||||
|
||||
publicKeyString := common.PubkeyToHex(&publicKey)
|
||||
mentioned := false
|
||||
for _, mention := range n.Message.Mentions {
|
||||
if publicKeyString == mention {
|
||||
mentioned = true
|
||||
}
|
||||
}
|
||||
|
||||
if mentioned {
|
||||
return true
|
||||
}
|
||||
|
||||
if responseTo != nil {
|
||||
return responseTo.From == publicKeyString
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// SetMailserver sets the currently used mailserver
|
||||
func (m *Messenger) SetMailserver(peer []byte) {
|
||||
m.mailserver = peer
|
||||
@ -3941,11 +3924,18 @@ func (m *Messenger) ValidateTransactions(ctx context.Context, addresses []types.
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.Notifications = append(response.Notifications, MessageNotificationBody{
|
||||
Message: message,
|
||||
Contact: contact,
|
||||
Chat: chat,
|
||||
})
|
||||
|
||||
notificationsEnabled, err := m.settings.GetNotificationsEnabled()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if notificationsEnabled {
|
||||
notification, err := NewMessageNotification(message.ID, message, chat, contact, m.allContacts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.AddNotification(notification)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -201,6 +201,25 @@ func (m *Messenger) RequestToJoinCommunity(request *requests.RequestToJoinCommun
|
||||
response := &MessengerResponse{RequestsToJoinCommunity: []*communities.RequestToJoin{requestToJoin}}
|
||||
response.AddCommunity(community)
|
||||
|
||||
// We send a push notification in the background
|
||||
go func() {
|
||||
if m.pushNotificationClient != nil {
|
||||
pks, err := community.CanManageUsersPublicKeys()
|
||||
if err != nil {
|
||||
m.logger.Error("failed to get pks", zap.Error(err))
|
||||
return
|
||||
}
|
||||
for _, publicKey := range pks {
|
||||
pkString := common.PubkeyToHex(publicKey)
|
||||
_, err = m.pushNotificationClient.SendNotification(publicKey, nil, requestToJoin.ID, pkString, protobuf.PushNotification_REQUEST_TO_JOIN_COMMUNITY)
|
||||
if err != nil {
|
||||
m.logger.Error("error sending notification", zap.Error(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
@ -314,7 +333,7 @@ func (m *Messenger) CreateCommunity(request *requests.CreateCommunity) (*Messeng
|
||||
}
|
||||
|
||||
description.Members = make(map[string]*protobuf.CommunityMember)
|
||||
description.Members[common.PubkeyToHex(&m.identity.PublicKey)] = &protobuf.CommunityMember{}
|
||||
description.Members[common.PubkeyToHex(&m.identity.PublicKey)] = &protobuf.CommunityMember{Roles: []protobuf.CommunityMember_Roles{protobuf.CommunityMember_ROLE_ALL}}
|
||||
|
||||
community, err := m.communitiesManager.CreateCommunity(description)
|
||||
if err != nil {
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"github.com/status-im/status-go/protocol/communities"
|
||||
"github.com/status-im/status-go/protocol/encryption/multidevice"
|
||||
"github.com/status-im/status-go/protocol/transport"
|
||||
localnotifications "github.com/status-im/status-go/services/local-notifications"
|
||||
"github.com/status-im/status-go/services/mailservers"
|
||||
)
|
||||
|
||||
@ -23,12 +24,13 @@ type MessengerResponse struct {
|
||||
Mailservers []mailservers.Mailserver
|
||||
MailserverTopics []mailservers.MailserverTopic
|
||||
MailserverRanges []mailservers.ChatRequestRange
|
||||
// Notifications a list of MessageNotificationBody derived from received messages that are useful to notify the user about
|
||||
Notifications []MessageNotificationBody
|
||||
|
||||
chats map[string]*Chat
|
||||
removedChats map[string]bool
|
||||
communities map[string]*communities.Community
|
||||
// notifications a list of notifications derived from messenger events
|
||||
// that are useful to notify the user about
|
||||
notifications map[string]*localnotifications.Notification
|
||||
chats map[string]*Chat
|
||||
removedChats map[string]bool
|
||||
communities map[string]*communities.Community
|
||||
}
|
||||
|
||||
func (r *MessengerResponse) MarshalJSON() ([]byte, error) {
|
||||
@ -47,9 +49,10 @@ func (r *MessengerResponse) MarshalJSON() ([]byte, error) {
|
||||
Mailservers []mailservers.Mailserver `json:"mailservers,omitempty"`
|
||||
MailserverTopics []mailservers.MailserverTopic `json:"mailserverTopics,omitempty"`
|
||||
MailserverRanges []mailservers.ChatRequestRange `json:"mailserverRanges,omitempty"`
|
||||
// Notifications a list of MessageNotificationBody derived from received messages that are useful to notify the user about
|
||||
Notifications []MessageNotificationBody `json:"notifications"`
|
||||
Communities []*communities.Community `json:"communities,omitempty"`
|
||||
// Notifications a list of notifications derived from messenger events
|
||||
// that are useful to notify the user about
|
||||
Notifications []*localnotifications.Notification `json:"notifications"`
|
||||
Communities []*communities.Community `json:"communities,omitempty"`
|
||||
}{
|
||||
Messages: r.Messages,
|
||||
Contacts: r.Contacts,
|
||||
@ -63,9 +66,9 @@ func (r *MessengerResponse) MarshalJSON() ([]byte, error) {
|
||||
Mailservers: r.Mailservers,
|
||||
MailserverTopics: r.MailserverTopics,
|
||||
MailserverRanges: r.MailserverRanges,
|
||||
Notifications: r.Notifications,
|
||||
}
|
||||
|
||||
responseItem.Notifications = r.Notifications()
|
||||
responseItem.Chats = r.Chats()
|
||||
responseItem.Communities = r.Communities()
|
||||
responseItem.RemovedChats = r.RemovedChats()
|
||||
@ -97,6 +100,14 @@ func (r *MessengerResponse) Communities() []*communities.Community {
|
||||
return communities
|
||||
}
|
||||
|
||||
func (r *MessengerResponse) Notifications() []*localnotifications.Notification {
|
||||
var notifications []*localnotifications.Notification
|
||||
for _, n := range r.notifications {
|
||||
notifications = append(notifications, n)
|
||||
}
|
||||
return notifications
|
||||
}
|
||||
|
||||
func (r *MessengerResponse) IsEmpty() bool {
|
||||
return len(r.chats)+
|
||||
len(r.Messages)+
|
||||
@ -112,7 +123,7 @@ func (r *MessengerResponse) IsEmpty() bool {
|
||||
len(r.MailserverTopics)+
|
||||
len(r.Mailservers)+
|
||||
len(r.MailserverRanges)+
|
||||
len(r.Notifications)+
|
||||
len(r.notifications)+
|
||||
len(r.RequestsToJoinCommunity) == 0
|
||||
}
|
||||
|
||||
@ -127,7 +138,6 @@ func (r *MessengerResponse) Merge(response *MessengerResponse) error {
|
||||
len(response.Mailservers)+
|
||||
len(response.MailserverTopics)+
|
||||
len(response.MailserverRanges)+
|
||||
len(response.Notifications)+
|
||||
len(response.EmojiReactions)+
|
||||
len(response.CommunityChanges) != 0 {
|
||||
return ErrNotImplemented
|
||||
@ -135,6 +145,7 @@ func (r *MessengerResponse) Merge(response *MessengerResponse) error {
|
||||
|
||||
r.AddChats(response.Chats())
|
||||
r.AddRemovedChats(response.RemovedChats())
|
||||
r.AddNotifications(response.Notifications())
|
||||
r.overrideMessages(response.Messages)
|
||||
r.overrideFilters(response.Filters)
|
||||
r.overrideRemovedFilters(response.Filters)
|
||||
@ -219,6 +230,24 @@ func (r *MessengerResponse) AddChats(chats []*Chat) {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *MessengerResponse) AddNotification(n *localnotifications.Notification) {
|
||||
if r.notifications == nil {
|
||||
r.notifications = make(map[string]*localnotifications.Notification)
|
||||
}
|
||||
|
||||
r.notifications[n.ID.String()] = n
|
||||
}
|
||||
|
||||
func (r *MessengerResponse) ClearNotifications() {
|
||||
r.notifications = nil
|
||||
}
|
||||
|
||||
func (r *MessengerResponse) AddNotifications(notifications []*localnotifications.Notification) {
|
||||
for _, c := range notifications {
|
||||
r.AddNotification(c)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *MessengerResponse) AddRemovedChats(chats []string) {
|
||||
for _, c := range chats {
|
||||
r.AddRemovedChat(c)
|
||||
|
@ -88,18 +88,21 @@ const (
|
||||
PushNotification_UNKNOWN_PUSH_NOTIFICATION_TYPE PushNotification_PushNotificationType = 0
|
||||
PushNotification_MESSAGE PushNotification_PushNotificationType = 1
|
||||
PushNotification_MENTION PushNotification_PushNotificationType = 2
|
||||
PushNotification_REQUEST_TO_JOIN_COMMUNITY PushNotification_PushNotificationType = 3
|
||||
)
|
||||
|
||||
var PushNotification_PushNotificationType_name = map[int32]string{
|
||||
0: "UNKNOWN_PUSH_NOTIFICATION_TYPE",
|
||||
1: "MESSAGE",
|
||||
2: "MENTION",
|
||||
3: "REQUEST_TO_JOIN_COMMUNITY",
|
||||
}
|
||||
|
||||
var PushNotification_PushNotificationType_value = map[string]int32{
|
||||
"UNKNOWN_PUSH_NOTIFICATION_TYPE": 0,
|
||||
"MESSAGE": 1,
|
||||
"MENTION": 2,
|
||||
"REQUEST_TO_JOIN_COMMUNITY": 3,
|
||||
}
|
||||
|
||||
func (x PushNotification_PushNotificationType) String() string {
|
||||
@ -833,70 +836,72 @@ func init() {
|
||||
}
|
||||
|
||||
var fileDescriptor_200acd86044eaa5d = []byte{
|
||||
// 1038 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x55, 0xdd, 0x6e, 0xe3, 0x44,
|
||||
0x14, 0xc6, 0x4e, 0xda, 0x24, 0x27, 0x69, 0x9a, 0x0e, 0x6d, 0x6a, 0x0a, 0x5d, 0x82, 0x01, 0x11,
|
||||
0xf5, 0xa2, 0xa0, 0x22, 0xb1, 0x2b, 0xf6, 0x86, 0x90, 0xba, 0x5d, 0xab, 0x8d, 0x1d, 0x26, 0x2e,
|
||||
0xab, 0x95, 0x90, 0x2c, 0xd7, 0x9e, 0xb6, 0x56, 0x53, 0x8f, 0xf1, 0x4c, 0x8a, 0x72, 0xc7, 0x03,
|
||||
0x70, 0xc3, 0x2d, 0x57, 0x3c, 0xc3, 0xbe, 0x12, 0x2f, 0x82, 0x3c, 0x1e, 0xa7, 0x6e, 0x93, 0xa6,
|
||||
0x45, 0xe2, 0x2a, 0x39, 0xdf, 0xf9, 0x99, 0xf3, 0xfb, 0x19, 0xb4, 0x78, 0xc2, 0xae, 0xdc, 0x88,
|
||||
0xf2, 0xf0, 0x22, 0xf4, 0x3d, 0x1e, 0xd2, 0x88, 0xed, 0xc7, 0x09, 0xe5, 0x14, 0x55, 0xc5, 0xcf,
|
||||
0xf9, 0xe4, 0x62, 0xe7, 0x43, 0xff, 0xca, 0xe3, 0x6e, 0x18, 0x90, 0x88, 0x87, 0x7c, 0x9a, 0xa9,
|
||||
0xf5, 0xbf, 0x57, 0xe0, 0x93, 0xe1, 0x84, 0x5d, 0x59, 0x05, 0x57, 0x4c, 0x2e, 0x43, 0xc6, 0x13,
|
||||
0xf1, 0x1f, 0xd9, 0x00, 0x9c, 0x5e, 0x93, 0xc8, 0xe5, 0xd3, 0x98, 0x68, 0x4a, 0x47, 0xe9, 0x36,
|
||||
0x0f, 0xbe, 0xd9, 0xcf, 0x83, 0xee, 0x2f, 0xf3, 0xdd, 0x77, 0x52, 0x47, 0x67, 0x1a, 0x13, 0x5c,
|
||||
0xe3, 0xf9, 0x5f, 0xf4, 0x19, 0x34, 0x02, 0x72, 0x1b, 0xfa, 0xc4, 0x15, 0x98, 0xa6, 0x76, 0x94,
|
||||
0x6e, 0x0d, 0xd7, 0x33, 0x4c, 0x78, 0xa0, 0xaf, 0x60, 0x3d, 0x8c, 0x18, 0xf7, 0xc6, 0x63, 0x11,
|
||||
0xc7, 0x0d, 0x03, 0xad, 0x24, 0xac, 0x9a, 0x45, 0xd8, 0x0c, 0xd2, 0x58, 0x9e, 0xef, 0x13, 0xc6,
|
||||
0x64, 0xac, 0x72, 0x16, 0x2b, 0xc3, 0xb2, 0x58, 0x1a, 0x54, 0x48, 0xe4, 0x9d, 0x8f, 0x49, 0xa0,
|
||||
0xad, 0x74, 0x94, 0x6e, 0x15, 0xe7, 0x62, 0xaa, 0xb9, 0x25, 0x09, 0x0b, 0x69, 0xa4, 0xad, 0x76,
|
||||
0x94, 0x6e, 0x19, 0xe7, 0x22, 0xea, 0x42, 0xcb, 0x1b, 0x8f, 0xe9, 0x6f, 0x24, 0x70, 0xaf, 0xc9,
|
||||
0xd4, 0x1d, 0x87, 0x8c, 0x6b, 0x95, 0x4e, 0xa9, 0xdb, 0xc0, 0x4d, 0x89, 0x9f, 0x90, 0xe9, 0x69,
|
||||
0xc8, 0x38, 0xda, 0x83, 0x8d, 0xf3, 0x31, 0xf5, 0xaf, 0x49, 0xe0, 0x8a, 0xee, 0x0a, 0xd3, 0xaa,
|
||||
0x30, 0x5d, 0x97, 0x8a, 0xfe, 0x95, 0xc7, 0x85, 0xed, 0x0b, 0x80, 0x49, 0x94, 0x88, 0xfe, 0x90,
|
||||
0x44, 0xab, 0x89, 0x64, 0x0a, 0x08, 0xda, 0x84, 0x95, 0xcb, 0xc4, 0x8b, 0xb8, 0x06, 0x1d, 0xa5,
|
||||
0xdb, 0xc0, 0x99, 0x80, 0x5e, 0x82, 0x26, 0xde, 0x74, 0x2f, 0x12, 0x7a, 0xe3, 0xfa, 0x34, 0xe2,
|
||||
0x9e, 0xcf, 0x99, 0x4b, 0xa3, 0xf1, 0x54, 0xab, 0x8b, 0x18, 0x5b, 0x42, 0x7f, 0x94, 0xd0, 0x9b,
|
||||
0xbe, 0xd4, 0xda, 0xd1, 0x78, 0x8a, 0x3e, 0x86, 0x9a, 0x17, 0x47, 0x2e, 0xa7, 0x71, 0xe8, 0x6b,
|
||||
0x0d, 0xd1, 0x98, 0xaa, 0x17, 0x47, 0x4e, 0x2a, 0xa3, 0x2f, 0xa1, 0x29, 0xd2, 0x73, 0x6f, 0xd2,
|
||||
0x6d, 0xa0, 0x11, 0xd3, 0xd6, 0x44, 0xac, 0x35, 0x81, 0x0e, 0x24, 0x88, 0x5e, 0xc3, 0x4e, 0xde,
|
||||
0x88, 0xdc, 0xb0, 0x50, 0x67, 0x53, 0xd4, 0xb9, 0x2d, 0x2d, 0x72, 0xa7, 0xbc, 0x5e, 0xfd, 0x08,
|
||||
0x6a, 0xb3, 0x05, 0x40, 0x6d, 0x40, 0x67, 0xd6, 0x89, 0x65, 0xbf, 0xb5, 0x5c, 0xc7, 0x3e, 0x31,
|
||||
0x2c, 0xd7, 0x79, 0x37, 0x34, 0x5a, 0x1f, 0xa0, 0x35, 0xa8, 0xf5, 0x86, 0x12, 0x6b, 0x29, 0x08,
|
||||
0x41, 0xf3, 0xc8, 0xc4, 0xc6, 0x8f, 0xbd, 0x91, 0x21, 0x31, 0x55, 0x7f, 0xaf, 0xc2, 0x17, 0xcb,
|
||||
0xd6, 0x0c, 0x13, 0x16, 0xd3, 0x88, 0x91, 0x74, 0xa0, 0x6c, 0x22, 0x46, 0x2f, 0xf6, 0xb4, 0x8a,
|
||||
0x73, 0x11, 0x59, 0xb0, 0x42, 0x92, 0x84, 0x26, 0x62, 0xd9, 0x9a, 0x07, 0xaf, 0x9e, 0xb7, 0xbf,
|
||||
0x79, 0xe0, 0x7d, 0x23, 0xf5, 0x15, 0x7b, 0x9c, 0x85, 0x41, 0xbb, 0x00, 0x09, 0xf9, 0x75, 0x42,
|
||||
0x18, 0xcf, 0x77, 0xb3, 0x81, 0x6b, 0x12, 0x31, 0x03, 0xfd, 0x77, 0x05, 0x6a, 0x33, 0x9f, 0x62,
|
||||
0xe9, 0x06, 0xc6, 0x36, 0xce, 0x4b, 0xdf, 0x82, 0x8d, 0x41, 0xef, 0xf4, 0xc8, 0xc6, 0x03, 0xe3,
|
||||
0xd0, 0x1d, 0x18, 0xa3, 0x51, 0xef, 0xd8, 0x68, 0x29, 0x68, 0x13, 0x5a, 0x3f, 0x1b, 0x78, 0x64,
|
||||
0xda, 0x96, 0x3b, 0x30, 0x47, 0x83, 0x9e, 0xd3, 0x7f, 0xd3, 0x52, 0xd1, 0x0e, 0xb4, 0xcf, 0xac,
|
||||
0xd1, 0xd9, 0x70, 0x68, 0x63, 0xc7, 0x38, 0x2c, 0xf6, 0xb0, 0x94, 0x36, 0xcd, 0xb4, 0x1c, 0x03,
|
||||
0x5b, 0xbd, 0xd3, 0xec, 0x85, 0x56, 0x59, 0x7f, 0xaf, 0x80, 0x26, 0xd7, 0xa1, 0x4f, 0x03, 0xd2,
|
||||
0x0b, 0x6e, 0x49, 0xc2, 0x43, 0x46, 0xd2, 0x31, 0xa2, 0x77, 0xd0, 0x9e, 0xe3, 0x0b, 0x37, 0x8c,
|
||||
0x2e, 0xa8, 0xa6, 0x74, 0x4a, 0xdd, 0xfa, 0xc1, 0xe7, 0x8f, 0xf7, 0xe7, 0xa7, 0x09, 0x49, 0xa6,
|
||||
0x66, 0x74, 0x41, 0xf1, 0x66, 0xfc, 0x40, 0x95, 0xa2, 0xe8, 0x35, 0xac, 0xdd, 0xa3, 0x19, 0xd1,
|
||||
0xf1, 0xfa, 0x41, 0xfb, 0x2e, 0x62, 0xba, 0x1f, 0xa6, 0xd4, 0xe2, 0x86, 0x5f, 0x90, 0xf4, 0x57,
|
||||
0xb0, 0xb5, 0xf0, 0x3d, 0xf4, 0x29, 0xd4, 0xe3, 0xc9, 0xf9, 0x38, 0xf4, 0xd3, 0x7b, 0x64, 0x22,
|
||||
0xcb, 0x06, 0x86, 0x0c, 0x3a, 0x21, 0x53, 0xa6, 0xff, 0xa1, 0xc2, 0x47, 0x8f, 0xa6, 0x3a, 0x47,
|
||||
0x13, 0xca, 0x3c, 0x4d, 0x2c, 0xa0, 0x1c, 0x75, 0x21, 0xe5, 0xec, 0x02, 0xdc, 0xa5, 0x92, 0x8f,
|
||||
0x7e, 0x96, 0xc9, 0x42, 0xea, 0x28, 0x2f, 0xa4, 0x8e, 0xd9, 0xb9, 0xaf, 0x14, 0xcf, 0xfd, 0x71,
|
||||
0x52, 0xda, 0x83, 0x0d, 0x46, 0x92, 0x5b, 0x92, 0xb8, 0x85, 0xf7, 0x2b, 0xc2, 0x77, 0x3d, 0x53,
|
||||
0x0c, 0xf3, 0x2c, 0xf4, 0x3f, 0x15, 0xd8, 0x5d, 0xd8, 0x8e, 0xd9, 0xad, 0xbc, 0x84, 0xf2, 0x7f,
|
||||
0x1d, 0xb8, 0x70, 0x48, 0xeb, 0xbf, 0x21, 0x8c, 0x79, 0x97, 0x24, 0xef, 0x51, 0x03, 0xd7, 0x24,
|
||||
0x62, 0x06, 0xc5, 0x1b, 0x2c, 0xdd, 0xbb, 0x41, 0xfd, 0x1f, 0x15, 0x5a, 0x0f, 0x83, 0x3f, 0x67,
|
||||
0x32, 0xdb, 0x50, 0x91, 0x1b, 0x25, 0x5f, 0x5b, 0xcd, 0x76, 0xe6, 0xa9, 0x49, 0x2c, 0x98, 0x68,
|
||||
0x79, 0xe1, 0x44, 0x35, 0xa8, 0xc8, 0xfc, 0xe5, 0x28, 0x72, 0x11, 0xf5, 0xa1, 0x2c, 0xbe, 0x7a,
|
||||
0xab, 0x82, 0x35, 0xbe, 0x7e, 0xbc, 0x49, 0x73, 0x80, 0x20, 0x0b, 0xe1, 0x8c, 0xda, 0xb0, 0xea,
|
||||
0x4d, 0xf8, 0x15, 0x4d, 0xe4, 0xb0, 0xa4, 0xa4, 0x3b, 0xb0, 0xb9, 0xc8, 0x0b, 0xe9, 0xf0, 0x22,
|
||||
0xa7, 0x8b, 0xe1, 0xd9, 0xe8, 0x8d, 0x6b, 0xd9, 0x8e, 0x79, 0x64, 0xf6, 0x7b, 0x4e, 0xca, 0x08,
|
||||
0x92, 0x3a, 0xea, 0x50, 0xb9, 0x23, 0x0c, 0x21, 0x58, 0xa9, 0xba, 0xa5, 0xea, 0x31, 0x6c, 0xcf,
|
||||
0x53, 0x9a, 0xe0, 0x25, 0xf4, 0x1d, 0x54, 0x25, 0x45, 0x31, 0x39, 0xf6, 0x9d, 0x25, 0x3c, 0x38,
|
||||
0xb3, 0x7d, 0x62, 0xe2, 0xfa, 0x5f, 0x2a, 0xb4, 0xe7, 0x9f, 0x8c, 0x69, 0xc2, 0x97, 0x10, 0xf2,
|
||||
0x0f, 0xf7, 0x09, 0x79, 0x6f, 0x19, 0x21, 0xa7, 0xa1, 0x16, 0x52, 0xf0, 0xff, 0x31, 0x7d, 0xfd,
|
||||
0x97, 0xe7, 0x50, 0xf5, 0x3a, 0xd4, 0xdf, 0x62, 0xdb, 0x3a, 0x2e, 0x7e, 0xa7, 0x1e, 0x50, 0xae,
|
||||
0x9a, 0x62, 0x96, 0xed, 0xb8, 0xd8, 0x38, 0x36, 0x47, 0x8e, 0x81, 0x8d, 0xc3, 0x56, 0x49, 0x9f,
|
||||
0x80, 0x36, 0x5f, 0x90, 0x3c, 0xc1, 0xfb, 0x7d, 0x55, 0x1e, 0x5e, 0xd2, 0xf7, 0x50, 0x49, 0x44,
|
||||
0xed, 0x4c, 0x53, 0xc5, 0xb4, 0x3a, 0x4f, 0x35, 0x09, 0xe7, 0x0e, 0xe7, 0xab, 0xc2, 0xf2, 0xdb,
|
||||
0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x30, 0x4e, 0x6f, 0x73, 0x17, 0x0a, 0x00, 0x00,
|
||||
// 1064 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x55, 0x4d, 0x6f, 0xe3, 0xc4,
|
||||
0x1b, 0xff, 0x3b, 0x49, 0x9b, 0xe4, 0x49, 0x9a, 0x7a, 0xe7, 0xdf, 0x4d, 0xbd, 0x85, 0x2e, 0xc1,
|
||||
0x80, 0x88, 0x7a, 0x28, 0xa8, 0x48, 0xec, 0x8a, 0xbd, 0x10, 0x52, 0xb7, 0x6b, 0xda, 0xd8, 0xd9,
|
||||
0x89, 0xc3, 0xaa, 0x12, 0xd2, 0xc8, 0xb5, 0xa7, 0xad, 0xd5, 0xd4, 0x36, 0x9e, 0x49, 0x51, 0x6e,
|
||||
0x88, 0x33, 0x17, 0xae, 0x9c, 0xf8, 0x0c, 0xfb, 0x09, 0x91, 0xc7, 0xe3, 0xd4, 0x6d, 0xd2, 0x17,
|
||||
0x24, 0x4e, 0xc9, 0xf3, 0x7b, 0x5e, 0xe6, 0x79, 0xfd, 0x19, 0xb4, 0x78, 0xca, 0x2e, 0x48, 0x18,
|
||||
0xf1, 0xe0, 0x2c, 0xf0, 0x5c, 0x1e, 0x44, 0x21, 0xdb, 0x8d, 0x93, 0x88, 0x47, 0xa8, 0x26, 0x7e,
|
||||
0x4e, 0xa7, 0x67, 0x5b, 0xff, 0xf7, 0x2e, 0x5c, 0x4e, 0x02, 0x9f, 0x86, 0x3c, 0xe0, 0xb3, 0x4c,
|
||||
0xad, 0xff, 0xbd, 0x02, 0x1f, 0x0f, 0xa7, 0xec, 0xc2, 0x2a, 0xb8, 0x62, 0x7a, 0x1e, 0x30, 0x9e,
|
||||
0x88, 0xff, 0xc8, 0x06, 0xe0, 0xd1, 0x25, 0x0d, 0x09, 0x9f, 0xc5, 0x54, 0x53, 0x3a, 0x4a, 0xb7,
|
||||
0xb5, 0xf7, 0xf5, 0x6e, 0x1e, 0x74, 0xf7, 0x21, 0xdf, 0x5d, 0x27, 0x75, 0x74, 0x66, 0x31, 0xc5,
|
||||
0x75, 0x9e, 0xff, 0x45, 0x9f, 0x42, 0xd3, 0xa7, 0xd7, 0x81, 0x47, 0x89, 0xc0, 0xb4, 0x52, 0x47,
|
||||
0xe9, 0xd6, 0x71, 0x23, 0xc3, 0x84, 0x07, 0xfa, 0x12, 0xd6, 0x83, 0x90, 0x71, 0x77, 0x32, 0x11,
|
||||
0x71, 0x48, 0xe0, 0x6b, 0x65, 0x61, 0xd5, 0x2a, 0xc2, 0xa6, 0x9f, 0xc6, 0x72, 0x3d, 0x8f, 0x32,
|
||||
0x26, 0x63, 0x55, 0xb2, 0x58, 0x19, 0x96, 0xc5, 0xd2, 0xa0, 0x4a, 0x43, 0xf7, 0x74, 0x42, 0x7d,
|
||||
0x6d, 0xa5, 0xa3, 0x74, 0x6b, 0x38, 0x17, 0x53, 0xcd, 0x35, 0x4d, 0x58, 0x10, 0x85, 0xda, 0x6a,
|
||||
0x47, 0xe9, 0x56, 0x70, 0x2e, 0xa2, 0x2e, 0xa8, 0xee, 0x64, 0x12, 0xfd, 0x4a, 0x7d, 0x72, 0x49,
|
||||
0x67, 0x64, 0x12, 0x30, 0xae, 0x55, 0x3b, 0xe5, 0x6e, 0x13, 0xb7, 0x24, 0x7e, 0x44, 0x67, 0xc7,
|
||||
0x01, 0xe3, 0x68, 0x07, 0x9e, 0x9d, 0x4e, 0x22, 0xef, 0x92, 0xfa, 0x44, 0x74, 0x57, 0x98, 0xd6,
|
||||
0x84, 0xe9, 0xba, 0x54, 0xf4, 0x2f, 0x5c, 0x2e, 0x6c, 0x5f, 0x02, 0x4c, 0xc3, 0x44, 0xf4, 0x87,
|
||||
0x26, 0x5a, 0x5d, 0x24, 0x53, 0x40, 0xd0, 0x06, 0xac, 0x9c, 0x27, 0x6e, 0xc8, 0x35, 0xe8, 0x28,
|
||||
0xdd, 0x26, 0xce, 0x04, 0xf4, 0x0a, 0x34, 0xf1, 0x26, 0x39, 0x4b, 0xa2, 0x2b, 0xe2, 0x45, 0x21,
|
||||
0x77, 0x3d, 0xce, 0x48, 0x14, 0x4e, 0x66, 0x5a, 0x43, 0xc4, 0x78, 0x2e, 0xf4, 0x07, 0x49, 0x74,
|
||||
0xd5, 0x97, 0x5a, 0x3b, 0x9c, 0xcc, 0xd0, 0x47, 0x50, 0x77, 0xe3, 0x90, 0xf0, 0x28, 0x0e, 0x3c,
|
||||
0xad, 0x29, 0x1a, 0x53, 0x73, 0xe3, 0xd0, 0x49, 0x65, 0xf4, 0x05, 0xb4, 0x44, 0x7a, 0xe4, 0x2a,
|
||||
0xdd, 0x86, 0x28, 0x64, 0xda, 0x9a, 0x88, 0xb5, 0x26, 0xd0, 0x81, 0x04, 0xd1, 0x1b, 0xd8, 0xca,
|
||||
0x1b, 0x91, 0x1b, 0x16, 0xea, 0x6c, 0x89, 0x3a, 0x37, 0xa5, 0x45, 0xee, 0x94, 0xd7, 0xab, 0x1f,
|
||||
0x40, 0x7d, 0xbe, 0x00, 0xa8, 0x0d, 0x68, 0x6c, 0x1d, 0x59, 0xf6, 0x7b, 0x8b, 0x38, 0xf6, 0x91,
|
||||
0x61, 0x11, 0xe7, 0x64, 0x68, 0xa8, 0xff, 0x43, 0x6b, 0x50, 0xef, 0x0d, 0x25, 0xa6, 0x2a, 0x08,
|
||||
0x41, 0xeb, 0xc0, 0xc4, 0xc6, 0x0f, 0xbd, 0x91, 0x21, 0xb1, 0x92, 0xfe, 0xa1, 0x04, 0x9f, 0x3f,
|
||||
0xb4, 0x66, 0x98, 0xb2, 0x38, 0x0a, 0x19, 0x4d, 0x07, 0xca, 0xa6, 0x62, 0xf4, 0x62, 0x4f, 0x6b,
|
||||
0x38, 0x17, 0x91, 0x05, 0x2b, 0x34, 0x49, 0xa2, 0x44, 0x2c, 0x5b, 0x6b, 0xef, 0xf5, 0xd3, 0xf6,
|
||||
0x37, 0x0f, 0xbc, 0x6b, 0xa4, 0xbe, 0x62, 0x8f, 0xb3, 0x30, 0x68, 0x1b, 0x20, 0xa1, 0xbf, 0x4c,
|
||||
0x29, 0xe3, 0xf9, 0x6e, 0x36, 0x71, 0x5d, 0x22, 0xa6, 0xaf, 0xff, 0xa6, 0x40, 0x7d, 0xee, 0x53,
|
||||
0x2c, 0xdd, 0xc0, 0xd8, 0xc6, 0x79, 0xe9, 0xcf, 0xe1, 0xd9, 0xa0, 0x77, 0x7c, 0x60, 0xe3, 0x81,
|
||||
0xb1, 0x4f, 0x06, 0xc6, 0x68, 0xd4, 0x3b, 0x34, 0x54, 0x05, 0x6d, 0x80, 0xfa, 0x93, 0x81, 0x47,
|
||||
0xa6, 0x6d, 0x91, 0x81, 0x39, 0x1a, 0xf4, 0x9c, 0xfe, 0x5b, 0xb5, 0x84, 0xb6, 0xa0, 0x3d, 0xb6,
|
||||
0x46, 0xe3, 0xe1, 0xd0, 0xc6, 0x8e, 0xb1, 0x5f, 0xec, 0x61, 0x39, 0x6d, 0x9a, 0x69, 0x39, 0x06,
|
||||
0xb6, 0x7a, 0xc7, 0xd9, 0x0b, 0x6a, 0x45, 0xff, 0xa0, 0x80, 0x26, 0xd7, 0xa1, 0x1f, 0xf9, 0xb4,
|
||||
0xe7, 0x5f, 0xd3, 0x84, 0x07, 0x8c, 0xa6, 0x63, 0x44, 0x27, 0xd0, 0x5e, 0xe0, 0x0b, 0x12, 0x84,
|
||||
0x67, 0x91, 0xa6, 0x74, 0xca, 0xdd, 0xc6, 0xde, 0x67, 0xf7, 0xf7, 0xe7, 0xdd, 0x94, 0x26, 0x33,
|
||||
0x33, 0x3c, 0x8b, 0xf0, 0x46, 0x7c, 0x47, 0x95, 0xa2, 0xe8, 0x0d, 0xac, 0xdd, 0xa2, 0x19, 0xd1,
|
||||
0xf1, 0xc6, 0x5e, 0xfb, 0x26, 0x62, 0xba, 0x1f, 0xa6, 0xd4, 0xe2, 0xa6, 0x57, 0x90, 0xf4, 0xd7,
|
||||
0xf0, 0x7c, 0xe9, 0x7b, 0xe8, 0x13, 0x68, 0xc4, 0xd3, 0xd3, 0x49, 0xe0, 0xa5, 0xf7, 0xc8, 0x44,
|
||||
0x96, 0x4d, 0x0c, 0x19, 0x74, 0x44, 0x67, 0x4c, 0xff, 0xa3, 0x04, 0x2f, 0xee, 0x4d, 0x75, 0x81,
|
||||
0x26, 0x94, 0x45, 0x9a, 0x58, 0x42, 0x39, 0xa5, 0xa5, 0x94, 0xb3, 0x0d, 0x70, 0x93, 0x4a, 0x3e,
|
||||
0xfa, 0x79, 0x26, 0x4b, 0xa9, 0xa3, 0xb2, 0x94, 0x3a, 0xe6, 0xe7, 0xbe, 0x52, 0x3c, 0xf7, 0xfb,
|
||||
0x49, 0x69, 0x07, 0x9e, 0x31, 0x9a, 0x5c, 0xd3, 0x84, 0x14, 0xde, 0xaf, 0x0a, 0xdf, 0xf5, 0x4c,
|
||||
0x31, 0xcc, 0xb3, 0xd0, 0xff, 0x54, 0x60, 0x7b, 0x69, 0x3b, 0xe6, 0xb7, 0xf2, 0x0a, 0x2a, 0xff,
|
||||
0x76, 0xe0, 0xc2, 0x21, 0xad, 0xff, 0x8a, 0x32, 0xe6, 0x9e, 0xd3, 0xbc, 0x47, 0x4d, 0x5c, 0x97,
|
||||
0x88, 0xe9, 0x17, 0x6f, 0xb0, 0x7c, 0xeb, 0x06, 0xf5, 0xdf, 0xcb, 0xa0, 0xde, 0x0d, 0xfe, 0x94,
|
||||
0xc9, 0x6c, 0x42, 0x55, 0x6e, 0x94, 0x7c, 0x6d, 0x35, 0xdb, 0x99, 0xc7, 0x26, 0xb1, 0x64, 0xa2,
|
||||
0x95, 0xa5, 0x13, 0xd5, 0xa0, 0x2a, 0xf3, 0x97, 0xa3, 0xc8, 0x45, 0xd4, 0x87, 0x8a, 0xf8, 0xea,
|
||||
0xad, 0x0a, 0xd6, 0xf8, 0xea, 0xfe, 0x26, 0x2d, 0x00, 0x82, 0x2c, 0x84, 0x33, 0x6a, 0xc3, 0xaa,
|
||||
0x3b, 0xe5, 0x17, 0x51, 0x22, 0x87, 0x25, 0x25, 0x9d, 0xc1, 0xc6, 0x32, 0x2f, 0xa4, 0xc3, 0xcb,
|
||||
0x9c, 0x2e, 0x86, 0xe3, 0xd1, 0x5b, 0x62, 0xd9, 0x8e, 0x79, 0x60, 0xf6, 0x7b, 0x4e, 0xca, 0x08,
|
||||
0x92, 0x3a, 0x1a, 0x50, 0xbd, 0x21, 0x0c, 0x21, 0x58, 0xa9, 0x5a, 0x2d, 0xa1, 0x6d, 0x78, 0x81,
|
||||
0x8d, 0x77, 0x63, 0x63, 0xe4, 0x10, 0xc7, 0x26, 0x3f, 0xda, 0xa6, 0x45, 0xfa, 0xf6, 0x60, 0x30,
|
||||
0xb6, 0x4c, 0xe7, 0x44, 0x2d, 0xeb, 0x31, 0x6c, 0x2e, 0x32, 0x9e, 0xa0, 0x2d, 0xf4, 0x2d, 0xd4,
|
||||
0x24, 0x83, 0x31, 0xb9, 0x15, 0x5b, 0x0f, 0xd0, 0xe4, 0xdc, 0xf6, 0x91, 0x85, 0xd0, 0xff, 0x2a,
|
||||
0x41, 0x7b, 0xf1, 0xc9, 0x38, 0x4a, 0xf8, 0x03, 0x7c, 0xfd, 0xfd, 0x6d, 0xbe, 0xde, 0x79, 0x88,
|
||||
0xaf, 0xd3, 0x50, 0x4b, 0x19, 0xfa, 0xbf, 0x58, 0x0e, 0xfd, 0xe7, 0xa7, 0x30, 0xf9, 0x3a, 0x34,
|
||||
0xde, 0x63, 0xdb, 0x3a, 0x2c, 0x7e, 0xc6, 0xee, 0x30, 0x72, 0x29, 0xc5, 0x2c, 0xdb, 0x21, 0xd8,
|
||||
0x38, 0x34, 0x47, 0x8e, 0x81, 0x8d, 0x7d, 0xb5, 0xac, 0x4f, 0x41, 0x5b, 0x2c, 0x48, 0x5e, 0xe8,
|
||||
0xed, 0xbe, 0x2a, 0x77, 0x0f, 0xed, 0x3b, 0xa8, 0x26, 0xa2, 0x76, 0xa6, 0x95, 0xc4, 0xb4, 0x3a,
|
||||
0x8f, 0x35, 0x09, 0xe7, 0x0e, 0xa7, 0xab, 0xc2, 0xf2, 0x9b, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff,
|
||||
0x78, 0x43, 0x10, 0x6f, 0x36, 0x0a, 0x00, 0x00,
|
||||
}
|
||||
|
@ -76,6 +76,7 @@ message PushNotification {
|
||||
UNKNOWN_PUSH_NOTIFICATION_TYPE = 0;
|
||||
MESSAGE = 1;
|
||||
MENTION = 2;
|
||||
REQUEST_TO_JOIN_COMMUNITY = 3;
|
||||
}
|
||||
bytes author = 7;
|
||||
}
|
||||
|
@ -14,9 +14,11 @@ import (
|
||||
"github.com/status-im/status-go/eth-node/crypto"
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
"github.com/status-im/status-go/protocol/common"
|
||||
"github.com/status-im/status-go/protocol/communities"
|
||||
"github.com/status-im/status-go/protocol/protobuf"
|
||||
"github.com/status-im/status-go/protocol/pushnotificationclient"
|
||||
"github.com/status-im/status-go/protocol/pushnotificationserver"
|
||||
"github.com/status-im/status-go/protocol/requests"
|
||||
"github.com/status-im/status-go/protocol/tt"
|
||||
"github.com/status-im/status-go/waku"
|
||||
)
|
||||
@ -843,3 +845,136 @@ func (s *MessengerPushNotificationSuite) TestReceivePushNotificationMention() {
|
||||
s.Require().NoError(alice.Shutdown())
|
||||
s.Require().NoError(server.Shutdown())
|
||||
}
|
||||
|
||||
func (s *MessengerPushNotificationSuite) TestReceivePushNotificationCommunityRequest() {
|
||||
|
||||
bob := s.m
|
||||
|
||||
serverKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
server := s.newPushNotificationServer(s.shh, serverKey)
|
||||
|
||||
alice := s.newMessenger(s.shh)
|
||||
// start alice and enable sending push notifications
|
||||
_, err = alice.Start()
|
||||
s.Require().NoError(err)
|
||||
s.Require().NoError(alice.EnableSendingPushNotifications())
|
||||
|
||||
// Register bob
|
||||
err = bob.AddPushNotificationsServer(context.Background(), &server.identity.PublicKey, pushnotificationclient.ServerTypeCustom)
|
||||
s.Require().NoError(err)
|
||||
|
||||
err = bob.RegisterForPushNotifications(context.Background(), bob1DeviceToken, testAPNTopic, protobuf.PushNotificationRegistration_APN_TOKEN)
|
||||
|
||||
// Pull servers and check we registered
|
||||
err = tt.RetryWithBackOff(func() error {
|
||||
_, err = server.RetrieveAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = bob.RetrieveAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
registered, err := bob.RegisteredForPushNotifications()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !registered {
|
||||
return errors.New("not registered")
|
||||
}
|
||||
|
||||
bobServers, err := bob.GetPushNotificationsServers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(bobServers) == 0 {
|
||||
return errors.New("not registered")
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
// Make sure we receive it
|
||||
s.Require().NoError(err)
|
||||
_, err = bob.GetPushNotificationsServers()
|
||||
s.Require().NoError(err)
|
||||
|
||||
description := &requests.CreateCommunity{
|
||||
Membership: protobuf.CommunityPermissions_ON_REQUEST,
|
||||
Name: "status",
|
||||
Color: "#ffffff",
|
||||
Description: "status community description",
|
||||
}
|
||||
|
||||
response, err := bob.CreateCommunity(description)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(response)
|
||||
s.Require().Len(response.Communities(), 1)
|
||||
community := response.Communities()[0]
|
||||
|
||||
// Send an community message
|
||||
chat := CreateOneToOneChat(common.PubkeyToHex(&alice.identity.PublicKey), &alice.identity.PublicKey, alice.transport)
|
||||
|
||||
inputMessage := &common.Message{}
|
||||
inputMessage.ChatId = chat.ID
|
||||
inputMessage.Text = "some text"
|
||||
inputMessage.CommunityID = community.IDString()
|
||||
|
||||
err = bob.SaveChat(chat)
|
||||
s.NoError(err)
|
||||
_, err = bob.SendChatMessage(context.Background(), inputMessage)
|
||||
s.NoError(err)
|
||||
|
||||
// Pull message and make sure org is received
|
||||
err = tt.RetryWithBackOff(func() error {
|
||||
response, err = alice.RetrieveAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(response.Communities()) == 0 {
|
||||
return errors.New("community not received")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
request := &requests.RequestToJoinCommunity{CommunityID: community.ID()}
|
||||
|
||||
// We try to join the org
|
||||
response, err = alice.RequestToJoinCommunity(request)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(response)
|
||||
s.Require().Len(response.RequestsToJoinCommunity, 1)
|
||||
|
||||
requestToJoin1 := response.RequestsToJoinCommunity[0]
|
||||
s.Require().NotNil(requestToJoin1)
|
||||
s.Require().Equal(community.ID(), requestToJoin1.CommunityID)
|
||||
s.Require().True(requestToJoin1.Our)
|
||||
s.Require().NotEmpty(requestToJoin1.ID)
|
||||
s.Require().NotEmpty(requestToJoin1.Clock)
|
||||
s.Require().Equal(requestToJoin1.PublicKey, common.PubkeyToHex(&alice.identity.PublicKey))
|
||||
s.Require().Equal(communities.RequestToJoinStatePending, requestToJoin1.State)
|
||||
|
||||
err = tt.RetryWithBackOff(func() error {
|
||||
_, err = server.RetrieveAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = alice.RetrieveAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if server.pushNotificationServer.SentRequests != 1 {
|
||||
return errors.New("request not sent")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
})
|
||||
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.Require().NoError(alice.Shutdown())
|
||||
s.Require().NoError(server.Shutdown())
|
||||
}
|
||||
|
@ -484,6 +484,7 @@ func (c *Client) HandlePushNotificationQueryResponse(serverPublicKey *ecdsa.Publ
|
||||
// get the public key associated with this query
|
||||
clientPublicKey, err := c.persistence.GetQueryPublicKey(response.MessageId)
|
||||
if err != nil {
|
||||
c.config.Logger.Error("failed to query client publicKey", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
if clientPublicKey == nil {
|
||||
@ -900,7 +901,7 @@ func (c *Client) handlePublicMessageSent(sentMessage *common.SentMessage) error
|
||||
|
||||
c.config.Logger.Debug("should no mention", zap.Any("publickey", shouldNotify))
|
||||
// we send the notifications and return the info of the devices notified
|
||||
infos, err := c.sendNotification(publicKey, nil, messageID, message.LocalChatID, protobuf.PushNotification_MENTION)
|
||||
infos, err := c.SendNotification(publicKey, nil, messageID, message.LocalChatID, protobuf.PushNotification_MENTION)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -998,7 +999,7 @@ func (c *Client) handleDirectMessageSent(sentMessage *common.SentMessage) error
|
||||
}
|
||||
|
||||
// we send the notifications and return the info of the devices notified
|
||||
infos, err := c.sendNotification(publicKey, installationIDs, trackedMessageIDs[0], chatID, protobuf.PushNotification_MESSAGE)
|
||||
infos, err := c.SendNotification(publicKey, installationIDs, trackedMessageIDs[0], chatID, protobuf.PushNotification_MESSAGE)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -1280,9 +1281,9 @@ func (c *Client) registerWithServer(registration *protobuf.PushNotificationRegis
|
||||
return nil
|
||||
}
|
||||
|
||||
// sendNotification sends an actual notification to the push notification server.
|
||||
// SendNotification sends an actual notification to the push notification server.
|
||||
// the notification is sent using an ephemeral key to shield the real identity of the sender
|
||||
func (c *Client) sendNotification(publicKey *ecdsa.PublicKey, installationIDs []string, messageID []byte, chatID string, notificationType protobuf.PushNotification_PushNotificationType) ([]*PushNotificationInfo, error) {
|
||||
func (c *Client) SendNotification(publicKey *ecdsa.PublicKey, installationIDs []string, messageID []byte, chatID string, notificationType protobuf.PushNotification_PushNotificationType) ([]*PushNotificationInfo, error) {
|
||||
|
||||
// get latest push notification infos
|
||||
err := c.queryNotificationInfo(publicKey, false)
|
||||
@ -1316,6 +1317,8 @@ func (c *Client) sendNotification(publicKey *ecdsa.PublicKey, installationIDs []
|
||||
|
||||
// one info per installation id, grouped by server
|
||||
actionableInfos := make(map[string][]*PushNotificationInfo)
|
||||
|
||||
c.config.Logger.Info("INFOS", zap.Any("info", info))
|
||||
for _, i := range info {
|
||||
|
||||
if !installationIDsMap[i.InstallationID] {
|
||||
@ -1406,7 +1409,7 @@ func (c *Client) resendNotification(pn *SentNotification) error {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.sendNotification(pn.PublicKey, []string{pn.InstallationID}, pn.MessageID, pn.ChatID, pn.NotificationType)
|
||||
_, err = c.SendNotification(pn.PublicKey, []string{pn.InstallationID}, pn.MessageID, pn.ChatID, pn.NotificationType)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
|
||||
const defaultNewMessageNotificationText = "You have a new message"
|
||||
const defaultMentionNotificationText = "Someone mentioned you"
|
||||
const defaultRequestToJoinCommunityNotificationText = "Someone requested to join a community you are an admin of"
|
||||
|
||||
type GoRushRequestData struct {
|
||||
EncryptedMessage string `json:"encryptedMessage"`
|
||||
@ -56,6 +57,8 @@ func PushNotificationRegistrationToGoRushRequest(requestAndRegistrations []*Requ
|
||||
var text string
|
||||
if request.Type == protobuf.PushNotification_MESSAGE {
|
||||
text = defaultNewMessageNotificationText
|
||||
} else if request.Type == protobuf.PushNotification_REQUEST_TO_JOIN_COMMUNITY {
|
||||
text = defaultRequestToJoinCommunityNotificationText
|
||||
} else {
|
||||
text = defaultMentionNotificationText
|
||||
}
|
||||
|
@ -37,6 +37,8 @@ type Server struct {
|
||||
persistence Persistence
|
||||
config *Config
|
||||
messageProcessor *common.MessageProcessor
|
||||
// SentRequests keeps track of the requests sent to gorush, for testing only
|
||||
SentRequests int64
|
||||
}
|
||||
|
||||
func New(config *Config, persistence Persistence, messageProcessor *common.MessageProcessor) *Server {
|
||||
@ -376,7 +378,7 @@ func (s *Server) buildPushNotificationReport(pn *protobuf.PushNotification, regi
|
||||
} else if registration.AccessToken != pn.AccessToken {
|
||||
s.config.Logger.Debug("invalid token")
|
||||
report.Error = protobuf.PushNotificationReport_WRONG_TOKEN
|
||||
} else if (s.isMessageNotification(pn) && !s.isValidMessageNotification(pn, registration)) || (s.isMentionNotification(pn) && !s.isValidMentionNotification(pn, registration)) {
|
||||
} else if (s.isMessageNotification(pn) && !s.isValidMessageNotification(pn, registration)) || (s.isMentionNotification(pn) && !s.isValidMentionNotification(pn, registration)) || (s.isRequestToJoinCommunityNotification(pn) && !s.isValidRequestToJoinCommunityNotification(pn, registration)) {
|
||||
s.config.Logger.Debug("filtered notification")
|
||||
// We report as successful but don't send the notification
|
||||
// for privacy reasons, as otherwise we would disclose that
|
||||
@ -450,6 +452,7 @@ func (s *Server) sendPushNotification(requestAndRegistrations []*RequestAndRegis
|
||||
if len(requestAndRegistrations) == 0 {
|
||||
return nil
|
||||
}
|
||||
s.SentRequests++
|
||||
goRushRequest := PushNotificationRegistrationToGoRushRequest(requestAndRegistrations)
|
||||
return sendGoRushNotification(goRushRequest, s.config.GorushURL, s.config.Logger)
|
||||
}
|
||||
@ -529,10 +532,21 @@ func (s *Server) isMessageNotification(pn *protobuf.PushNotification) bool {
|
||||
return pn.Type == protobuf.PushNotification_MESSAGE
|
||||
}
|
||||
|
||||
// isValidMentionNotification checks:
|
||||
// isValidMessageNotification checks:
|
||||
// this is a message
|
||||
// the chat is not muted
|
||||
// the author is not blocked
|
||||
func (s *Server) isValidMessageNotification(pn *protobuf.PushNotification, registration *protobuf.PushNotificationRegistration) bool {
|
||||
return s.isMessageNotification(pn) && !s.contains(registration.BlockedChatList, pn.ChatId) && !s.contains(registration.BlockedChatList, pn.Author)
|
||||
}
|
||||
|
||||
func (s *Server) isRequestToJoinCommunityNotification(pn *protobuf.PushNotification) bool {
|
||||
return pn.Type == protobuf.PushNotification_REQUEST_TO_JOIN_COMMUNITY
|
||||
}
|
||||
|
||||
// isValidRequestToJoinCommunityNotification checks:
|
||||
// this is a request to join a community
|
||||
// the author is not blocked
|
||||
func (s *Server) isValidRequestToJoinCommunityNotification(pn *protobuf.PushNotification, registration *protobuf.PushNotificationRegistration) bool {
|
||||
return s.isRequestToJoinCommunityNotification(pn) && !s.contains(registration.BlockedChatList, pn.Author)
|
||||
}
|
||||
|
@ -185,10 +185,11 @@ func (s *Service) StartMessenger() (*protocol.MessengerResponse, error) {
|
||||
|
||||
func publishMessengerResponse(response *protocol.MessengerResponse) {
|
||||
if !response.IsEmpty() {
|
||||
PublisherSignalHandler{}.NewMessages(response)
|
||||
localnotifications.SendMessageNotifications(response.Notifications)
|
||||
notifications := response.Notifications()
|
||||
// Clear notifications as not used for now
|
||||
response.Notifications = nil
|
||||
response.ClearNotifications()
|
||||
PublisherSignalHandler{}.NewMessages(response)
|
||||
localnotifications.PushMessages(notifications)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,92 +3,69 @@ package localnotifications
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
"github.com/status-im/status-go/multiaccounts/accounts"
|
||||
"github.com/status-im/status-go/protocol"
|
||||
"github.com/status-im/status-go/services/wallet"
|
||||
"github.com/status-im/status-go/signal"
|
||||
)
|
||||
|
||||
type PushCategory string
|
||||
|
||||
type transactionState string
|
||||
|
||||
type NotificationType string
|
||||
|
||||
const (
|
||||
walletDeeplinkPrefix = "status-im://wallet/"
|
||||
|
||||
failed transactionState = "failed"
|
||||
inbound transactionState = "inbound"
|
||||
outbound transactionState = "outbound"
|
||||
|
||||
CategoryTransaction PushCategory = "transaction"
|
||||
CategoryMessage PushCategory = "newMessage"
|
||||
|
||||
TypeTransaction NotificationType = "transaction"
|
||||
TypeMessage NotificationType = "message"
|
||||
)
|
||||
|
||||
var (
|
||||
marshalTypeMismatchErr = "notification type mismatch, expected '%s', Body could not be marshalled into this type"
|
||||
)
|
||||
|
||||
type notificationBody struct {
|
||||
State transactionState `json:"state"`
|
||||
From common.Address `json:"from"`
|
||||
To common.Address `json:"to"`
|
||||
FromAccount *accounts.Account `json:"fromAccount,omitempty"`
|
||||
ToAccount *accounts.Account `json:"toAccount,omitempty"`
|
||||
Value *hexutil.Big `json:"value"`
|
||||
ERC20 bool `json:"erc20"`
|
||||
Contract common.Address `json:"contract"`
|
||||
Network uint64 `json:"network"`
|
||||
type NotificationBody interface {
|
||||
json.Marshaler
|
||||
}
|
||||
|
||||
type Notification struct {
|
||||
ID common.Hash `json:"id"`
|
||||
Platform float32 `json:"platform,omitempty"`
|
||||
Body interface{}
|
||||
BodyType NotificationType `json:"bodyType"`
|
||||
Category PushCategory `json:"category,omitempty"`
|
||||
Deeplink string `json:"deepLink,omitempty"`
|
||||
Image string `json:"imageUrl,omitempty"`
|
||||
IsScheduled bool `json:"isScheduled,omitempty"`
|
||||
ScheduledTime string `json:"scheduleTime,omitempty"`
|
||||
ID common.Hash
|
||||
Platform float32
|
||||
Body NotificationBody
|
||||
BodyType NotificationType
|
||||
Title string
|
||||
Message string
|
||||
Category PushCategory
|
||||
Deeplink string
|
||||
Image string
|
||||
IsScheduled bool
|
||||
ScheduledTime string
|
||||
IsConversation bool
|
||||
IsGroupConversation bool
|
||||
ConversationID string
|
||||
Timestamp uint64
|
||||
Author NotificationAuthor
|
||||
}
|
||||
|
||||
type NotificationAuthor struct {
|
||||
ID string `json:"id"`
|
||||
Icon string `json:"icon"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// notificationAlias is an interim struct used for json un/marshalling
|
||||
type notificationAlias struct {
|
||||
ID common.Hash `json:"id"`
|
||||
Platform float32 `json:"platform,omitempty"`
|
||||
Body json.RawMessage `json:"body"`
|
||||
BodyType NotificationType `json:"bodyType"`
|
||||
Category PushCategory `json:"category,omitempty"`
|
||||
Deeplink string `json:"deepLink,omitempty"`
|
||||
Image string `json:"imageUrl,omitempty"`
|
||||
IsScheduled bool `json:"isScheduled,omitempty"`
|
||||
ScheduledTime string `json:"scheduleTime,omitempty"`
|
||||
}
|
||||
|
||||
// TransactionEvent - structure used to pass messages from wallet to bus
|
||||
type TransactionEvent struct {
|
||||
Type string `json:"type"`
|
||||
BlockNumber *big.Int `json:"block-number"`
|
||||
Accounts []common.Address `json:"accounts"`
|
||||
NewTransactionsPerAccount map[common.Address]int `json:"new-transactions"`
|
||||
ERC20 bool `json:"erc20"`
|
||||
MaxKnownBlocks map[common.Address]*big.Int `json:"max-known-blocks"`
|
||||
ID common.Hash `json:"id"`
|
||||
Platform float32 `json:"platform,omitempty"`
|
||||
Body json.RawMessage `json:"body"`
|
||||
BodyType NotificationType `json:"bodyType"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Category PushCategory `json:"category,omitempty"`
|
||||
Deeplink string `json:"deepLink,omitempty"`
|
||||
Image string `json:"imageUrl,omitempty"`
|
||||
IsScheduled bool `json:"isScheduled,omitempty"`
|
||||
ScheduledTime string `json:"scheduleTime,omitempty"`
|
||||
IsConversation bool `json:"isConversation,omitempty"`
|
||||
IsGroupConversation bool `json:"isGroupConversation,omitempty"`
|
||||
ConversationID string `json:"conversationId,omitempty"`
|
||||
Timestamp uint64 `json:"timestamp,omitempty"`
|
||||
Author NotificationAuthor `json:"notificationAuthor,omitempty"`
|
||||
}
|
||||
|
||||
// MessageEvent - structure used to pass messages from chat to bus
|
||||
@ -132,274 +109,49 @@ func NewService(appDB *sql.DB, network uint64) *Service {
|
||||
}
|
||||
|
||||
func (n *Notification) MarshalJSON() ([]byte, error) {
|
||||
|
||||
var body json.RawMessage
|
||||
var err error
|
||||
|
||||
switch n.BodyType {
|
||||
case TypeTransaction:
|
||||
if nb, ok := n.Body.(notificationBody); ok {
|
||||
body, err = json.Marshal(nb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf(marshalTypeMismatchErr, n.BodyType)
|
||||
if n.Body != nil {
|
||||
encodedBody, err := n.Body.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case TypeMessage:
|
||||
if nmb, ok := n.Body.(protocol.MessageNotificationBody); ok {
|
||||
body, err = json.Marshal(nmb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf(marshalTypeMismatchErr, n.BodyType)
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown NotificationType '%s'", n.BodyType)
|
||||
body = encodedBody
|
||||
}
|
||||
|
||||
alias := notificationAlias{
|
||||
n.ID,
|
||||
n.Platform,
|
||||
body,
|
||||
n.BodyType,
|
||||
n.Category,
|
||||
n.Deeplink,
|
||||
n.Image,
|
||||
n.IsScheduled,
|
||||
n.ScheduledTime,
|
||||
ID: n.ID,
|
||||
Platform: n.Platform,
|
||||
Body: body,
|
||||
BodyType: n.BodyType,
|
||||
Category: n.Category,
|
||||
Title: n.Title,
|
||||
Message: n.Message,
|
||||
Deeplink: n.Deeplink,
|
||||
Image: n.Image,
|
||||
IsScheduled: n.IsScheduled,
|
||||
ScheduledTime: n.ScheduledTime,
|
||||
IsConversation: n.IsConversation,
|
||||
IsGroupConversation: n.IsGroupConversation,
|
||||
ConversationID: n.ConversationID,
|
||||
Timestamp: n.Timestamp,
|
||||
Author: n.Author,
|
||||
}
|
||||
|
||||
return json.Marshal(alias)
|
||||
}
|
||||
|
||||
func (n *Notification) UnmarshalJSON(data []byte) error {
|
||||
var alias notificationAlias
|
||||
err := json.Unmarshal(data, &alias)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
n.BodyType = alias.BodyType
|
||||
n.Category = alias.Category
|
||||
n.Platform = alias.Platform
|
||||
n.ID = alias.ID
|
||||
n.Image = alias.Image
|
||||
n.Deeplink = alias.Deeplink
|
||||
n.IsScheduled = alias.IsScheduled
|
||||
n.ScheduledTime = alias.ScheduledTime
|
||||
|
||||
switch n.BodyType {
|
||||
case TypeTransaction:
|
||||
return n.unmarshalAndAttachBody(alias.Body, ¬ificationBody{})
|
||||
|
||||
case TypeMessage:
|
||||
return n.unmarshalAndAttachBody(alias.Body, &protocol.MessageNotificationBody{})
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unknown NotificationType '%s'", n.BodyType)
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Notification) unmarshalAndAttachBody(body json.RawMessage, bodyStruct interface{}) error {
|
||||
err := json.Unmarshal(body, &bodyStruct)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
n.Body = bodyStruct
|
||||
return nil
|
||||
}
|
||||
|
||||
func pushMessages(ns []*Notification) {
|
||||
func PushMessages(ns []*Notification) {
|
||||
for _, n := range ns {
|
||||
pushMessage(n)
|
||||
}
|
||||
}
|
||||
|
||||
func pushMessage(notification *Notification) {
|
||||
log.Info("Pushing a new push notification", "info", notification)
|
||||
log.Debug("Pushing a new push notification", "notification", notification)
|
||||
signal.SendLocalNotifications(notification)
|
||||
}
|
||||
|
||||
func (s *Service) buildTransactionNotification(rawTransfer wallet.Transfer) *Notification {
|
||||
log.Info("Handled a new transfer in buildTransactionNotification", "info", rawTransfer)
|
||||
|
||||
var deeplink string
|
||||
var state transactionState
|
||||
transfer := wallet.CastToTransferView(rawTransfer)
|
||||
|
||||
switch {
|
||||
case transfer.TxStatus == hexutil.Uint64(0):
|
||||
state = failed
|
||||
case transfer.Address == transfer.To:
|
||||
state = inbound
|
||||
default:
|
||||
state = outbound
|
||||
}
|
||||
|
||||
from, err := s.accountsDB.GetAccountByAddress(types.Address(transfer.From))
|
||||
|
||||
if err != nil {
|
||||
log.Debug("Could not select From account by address", "error", err)
|
||||
}
|
||||
|
||||
to, err := s.accountsDB.GetAccountByAddress(types.Address(transfer.To))
|
||||
|
||||
if err != nil {
|
||||
log.Debug("Could not select To account by address", "error", err)
|
||||
}
|
||||
|
||||
if from != nil {
|
||||
deeplink = walletDeeplinkPrefix + from.Address.String()
|
||||
} else if to != nil {
|
||||
deeplink = walletDeeplinkPrefix + to.Address.String()
|
||||
}
|
||||
|
||||
body := notificationBody{
|
||||
State: state,
|
||||
From: transfer.From,
|
||||
To: transfer.Address,
|
||||
FromAccount: from,
|
||||
ToAccount: to,
|
||||
Value: transfer.Value,
|
||||
ERC20: string(transfer.Type) == "erc20",
|
||||
Contract: transfer.Contract,
|
||||
Network: transfer.NetworkID,
|
||||
}
|
||||
|
||||
return &Notification{
|
||||
BodyType: TypeTransaction,
|
||||
ID: transfer.ID,
|
||||
Body: body,
|
||||
Deeplink: deeplink,
|
||||
Category: CategoryTransaction,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) transactionsHandler(payload TransactionEvent) {
|
||||
log.Info("Handled a new transaction", "info", payload)
|
||||
|
||||
limit := 20
|
||||
if payload.BlockNumber != nil {
|
||||
for _, address := range payload.Accounts {
|
||||
if payload.BlockNumber.Cmp(payload.MaxKnownBlocks[address]) >= 0 {
|
||||
log.Info("Handled transfer for address", "info", address)
|
||||
transfers, err := s.walletDB.GetTransfersByAddressAndBlock(address, payload.BlockNumber, int64(limit))
|
||||
if err != nil {
|
||||
log.Error("Could not fetch transfers", "error", err)
|
||||
}
|
||||
|
||||
for _, transaction := range transfers {
|
||||
n := s.buildTransactionNotification(transaction)
|
||||
pushMessage(n)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SubscribeWallet - Subscribes to wallet signals
|
||||
func (s *Service) SubscribeWallet(publisher *event.Feed) error {
|
||||
s.walletTransmitter.publisher = publisher
|
||||
|
||||
preference, err := s.db.GetWalletPreference()
|
||||
|
||||
if err != nil {
|
||||
log.Error("Failed to get wallet preference", "error", err)
|
||||
s.WatchingEnabled = false
|
||||
} else {
|
||||
s.WatchingEnabled = preference.Enabled
|
||||
}
|
||||
|
||||
s.StartWalletWatcher()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartWalletWatcher - Forward wallet events to notifications
|
||||
func (s *Service) StartWalletWatcher() {
|
||||
if s.walletTransmitter.quit != nil {
|
||||
// already running, nothing to do
|
||||
return
|
||||
}
|
||||
|
||||
if s.walletTransmitter.publisher == nil {
|
||||
log.Error("wallet publisher was not initialized")
|
||||
return
|
||||
}
|
||||
|
||||
s.walletTransmitter.quit = make(chan struct{})
|
||||
events := make(chan wallet.Event, 10)
|
||||
sub := s.walletTransmitter.publisher.Subscribe(events)
|
||||
|
||||
s.walletTransmitter.wg.Add(1)
|
||||
|
||||
maxKnownBlocks := map[common.Address]*big.Int{}
|
||||
go func() {
|
||||
defer s.walletTransmitter.wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-s.walletTransmitter.quit:
|
||||
sub.Unsubscribe()
|
||||
return
|
||||
case err := <-sub.Err():
|
||||
// technically event.Feed cannot send an error to subscription.Err channel.
|
||||
// the only time we will get an event is when that channel is closed.
|
||||
if err != nil {
|
||||
log.Error("wallet signals transmitter failed with", "error", err)
|
||||
}
|
||||
return
|
||||
case event := <-events:
|
||||
if event.Type == wallet.EventNewBlock && len(maxKnownBlocks) > 0 {
|
||||
newBlocks := false
|
||||
for _, address := range event.Accounts {
|
||||
if _, ok := maxKnownBlocks[address]; !ok {
|
||||
newBlocks = true
|
||||
maxKnownBlocks[address] = event.BlockNumber
|
||||
} else if event.BlockNumber.Cmp(maxKnownBlocks[address]) == 1 {
|
||||
maxKnownBlocks[address] = event.BlockNumber
|
||||
newBlocks = true
|
||||
}
|
||||
}
|
||||
if newBlocks && s.WatchingEnabled {
|
||||
s.transmitter.publisher.Send(TransactionEvent{
|
||||
Type: string(event.Type),
|
||||
BlockNumber: event.BlockNumber,
|
||||
Accounts: event.Accounts,
|
||||
NewTransactionsPerAccount: event.NewTransactionsPerAccount,
|
||||
ERC20: event.ERC20,
|
||||
MaxKnownBlocks: maxKnownBlocks,
|
||||
})
|
||||
}
|
||||
} else if event.Type == wallet.EventMaxKnownBlock {
|
||||
for _, address := range event.Accounts {
|
||||
if _, ok := maxKnownBlocks[address]; !ok {
|
||||
maxKnownBlocks[address] = event.BlockNumber
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// StopWalletWatcher - stops watching for new wallet events
|
||||
func (s *Service) StopWalletWatcher() {
|
||||
if s.walletTransmitter.quit != nil {
|
||||
close(s.walletTransmitter.quit)
|
||||
s.walletTransmitter.wg.Wait()
|
||||
s.walletTransmitter.quit = nil
|
||||
}
|
||||
}
|
||||
|
||||
// IsWatchingWallet - check if local-notifications are subscribed to wallet updates
|
||||
func (s *Service) IsWatchingWallet() bool {
|
||||
return s.walletTransmitter.quit != nil
|
||||
}
|
||||
|
||||
// Start Worker which processes all incoming messages
|
||||
func (s *Service) Start(_ *p2p.Server) error {
|
||||
s.started = true
|
||||
@ -473,19 +225,3 @@ func (s *Service) Protocols() []p2p.Protocol {
|
||||
func (s *Service) IsStarted() bool {
|
||||
return s.started
|
||||
}
|
||||
|
||||
func SendMessageNotifications(mnb []protocol.MessageNotificationBody) {
|
||||
var ns []*Notification
|
||||
for _, n := range mnb {
|
||||
ns = append(ns, &Notification{
|
||||
Body: n,
|
||||
BodyType: TypeMessage,
|
||||
Category: CategoryMessage,
|
||||
Deeplink: "", // TODO find what if any Deeplink should be used here
|
||||
Image: "", // TODO do we want to attach any image data contained on the MessageBody{}?
|
||||
})
|
||||
}
|
||||
|
||||
// sends notifications messages to the OS level application
|
||||
pushMessages(ns)
|
||||
}
|
||||
|
@ -2,9 +2,9 @@ package localnotifications
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -128,19 +128,8 @@ func TestTransactionNotification(t *testing.T) {
|
||||
if signalEvent == nil {
|
||||
return fmt.Errorf("signal was not handled")
|
||||
}
|
||||
notification := struct {
|
||||
Type string
|
||||
Event Notification
|
||||
}{}
|
||||
|
||||
require.NoError(t, json.Unmarshal(signalEvent, ¬ification))
|
||||
|
||||
if notification.Type != "local-notifications" {
|
||||
return fmt.Errorf("wrong signal was sent")
|
||||
}
|
||||
if notification.Event.Body.(*notificationBody).To != header.Address {
|
||||
return fmt.Errorf("transaction to address is wrong")
|
||||
}
|
||||
require.True(t, strings.Contains(string(signalEvent), `"type":"local-notifications"`))
|
||||
require.True(t, strings.Contains(string(signalEvent), `"to":"`+header.Address.Hex()))
|
||||
return nil
|
||||
}, 2*time.Second, 100*time.Millisecond))
|
||||
|
||||
|
229
services/local-notifications/transaction.go
Normal file
229
services/local-notifications/transaction.go
Normal file
@ -0,0 +1,229 @@
|
||||
package localnotifications
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
"github.com/status-im/status-go/multiaccounts/accounts"
|
||||
"github.com/status-im/status-go/services/wallet"
|
||||
)
|
||||
|
||||
type transactionState string
|
||||
|
||||
const (
|
||||
walletDeeplinkPrefix = "status-im://wallet/"
|
||||
|
||||
failed transactionState = "failed"
|
||||
inbound transactionState = "inbound"
|
||||
outbound transactionState = "outbound"
|
||||
)
|
||||
|
||||
// TransactionEvent - structure used to pass messages from wallet to bus
|
||||
type TransactionEvent struct {
|
||||
Type string `json:"type"`
|
||||
BlockNumber *big.Int `json:"block-number"`
|
||||
Accounts []common.Address `json:"accounts"`
|
||||
NewTransactionsPerAccount map[common.Address]int `json:"new-transactions"`
|
||||
ERC20 bool `json:"erc20"`
|
||||
MaxKnownBlocks map[common.Address]*big.Int `json:"max-known-blocks"`
|
||||
}
|
||||
|
||||
type transactionBody struct {
|
||||
State transactionState `json:"state"`
|
||||
From common.Address `json:"from"`
|
||||
To common.Address `json:"to"`
|
||||
FromAccount *accounts.Account `json:"fromAccount,omitempty"`
|
||||
ToAccount *accounts.Account `json:"toAccount,omitempty"`
|
||||
Value *hexutil.Big `json:"value"`
|
||||
ERC20 bool `json:"erc20"`
|
||||
Contract common.Address `json:"contract"`
|
||||
Network uint64 `json:"network"`
|
||||
}
|
||||
|
||||
func (t transactionBody) MarshalJSON() ([]byte, error) {
|
||||
type Alias transactionBody
|
||||
item := struct{ *Alias }{Alias: (*Alias)(&t)}
|
||||
return json.Marshal(item)
|
||||
}
|
||||
|
||||
func (s *Service) buildTransactionNotification(rawTransfer wallet.Transfer) *Notification {
|
||||
log.Info("Handled a new transfer in buildTransactionNotification", "info", rawTransfer)
|
||||
|
||||
var deeplink string
|
||||
var state transactionState
|
||||
transfer := wallet.CastToTransferView(rawTransfer)
|
||||
|
||||
switch {
|
||||
case transfer.TxStatus == hexutil.Uint64(0):
|
||||
state = failed
|
||||
case transfer.Address == transfer.To:
|
||||
state = inbound
|
||||
default:
|
||||
state = outbound
|
||||
}
|
||||
|
||||
from, err := s.accountsDB.GetAccountByAddress(types.Address(transfer.From))
|
||||
|
||||
if err != nil {
|
||||
log.Debug("Could not select From account by address", "error", err)
|
||||
}
|
||||
|
||||
to, err := s.accountsDB.GetAccountByAddress(types.Address(transfer.To))
|
||||
|
||||
if err != nil {
|
||||
log.Debug("Could not select To account by address", "error", err)
|
||||
}
|
||||
|
||||
if from != nil {
|
||||
deeplink = walletDeeplinkPrefix + from.Address.String()
|
||||
} else if to != nil {
|
||||
deeplink = walletDeeplinkPrefix + to.Address.String()
|
||||
}
|
||||
|
||||
body := transactionBody{
|
||||
State: state,
|
||||
From: transfer.From,
|
||||
To: transfer.Address,
|
||||
FromAccount: from,
|
||||
ToAccount: to,
|
||||
Value: transfer.Value,
|
||||
ERC20: string(transfer.Type) == "erc20",
|
||||
Contract: transfer.Contract,
|
||||
Network: transfer.NetworkID,
|
||||
}
|
||||
|
||||
return &Notification{
|
||||
BodyType: TypeTransaction,
|
||||
ID: transfer.ID,
|
||||
Body: body,
|
||||
Deeplink: deeplink,
|
||||
Category: CategoryTransaction,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) transactionsHandler(payload TransactionEvent) {
|
||||
log.Info("Handled a new transaction", "info", payload)
|
||||
|
||||
limit := 20
|
||||
if payload.BlockNumber != nil {
|
||||
for _, address := range payload.Accounts {
|
||||
if payload.BlockNumber.Cmp(payload.MaxKnownBlocks[address]) >= 0 {
|
||||
log.Info("Handled transfer for address", "info", address)
|
||||
transfers, err := s.walletDB.GetTransfersByAddressAndBlock(address, payload.BlockNumber, int64(limit))
|
||||
if err != nil {
|
||||
log.Error("Could not fetch transfers", "error", err)
|
||||
}
|
||||
|
||||
for _, transaction := range transfers {
|
||||
n := s.buildTransactionNotification(transaction)
|
||||
pushMessage(n)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SubscribeWallet - Subscribes to wallet signals
|
||||
func (s *Service) SubscribeWallet(publisher *event.Feed) error {
|
||||
s.walletTransmitter.publisher = publisher
|
||||
|
||||
preference, err := s.db.GetWalletPreference()
|
||||
|
||||
if err != nil {
|
||||
log.Error("Failed to get wallet preference", "error", err)
|
||||
s.WatchingEnabled = false
|
||||
} else {
|
||||
s.WatchingEnabled = preference.Enabled
|
||||
}
|
||||
|
||||
s.StartWalletWatcher()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartWalletWatcher - Forward wallet events to notifications
|
||||
func (s *Service) StartWalletWatcher() {
|
||||
if s.walletTransmitter.quit != nil {
|
||||
// already running, nothing to do
|
||||
return
|
||||
}
|
||||
|
||||
if s.walletTransmitter.publisher == nil {
|
||||
log.Error("wallet publisher was not initialized")
|
||||
return
|
||||
}
|
||||
|
||||
s.walletTransmitter.quit = make(chan struct{})
|
||||
events := make(chan wallet.Event, 10)
|
||||
sub := s.walletTransmitter.publisher.Subscribe(events)
|
||||
|
||||
s.walletTransmitter.wg.Add(1)
|
||||
|
||||
maxKnownBlocks := map[common.Address]*big.Int{}
|
||||
go func() {
|
||||
defer s.walletTransmitter.wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-s.walletTransmitter.quit:
|
||||
sub.Unsubscribe()
|
||||
return
|
||||
case err := <-sub.Err():
|
||||
// technically event.Feed cannot send an error to subscription.Err channel.
|
||||
// the only time we will get an event is when that channel is closed.
|
||||
if err != nil {
|
||||
log.Error("wallet signals transmitter failed with", "error", err)
|
||||
}
|
||||
return
|
||||
case event := <-events:
|
||||
if event.Type == wallet.EventNewBlock && len(maxKnownBlocks) > 0 {
|
||||
newBlocks := false
|
||||
for _, address := range event.Accounts {
|
||||
if _, ok := maxKnownBlocks[address]; !ok {
|
||||
newBlocks = true
|
||||
maxKnownBlocks[address] = event.BlockNumber
|
||||
} else if event.BlockNumber.Cmp(maxKnownBlocks[address]) == 1 {
|
||||
maxKnownBlocks[address] = event.BlockNumber
|
||||
newBlocks = true
|
||||
}
|
||||
}
|
||||
if newBlocks && s.WatchingEnabled {
|
||||
s.transmitter.publisher.Send(TransactionEvent{
|
||||
Type: string(event.Type),
|
||||
BlockNumber: event.BlockNumber,
|
||||
Accounts: event.Accounts,
|
||||
NewTransactionsPerAccount: event.NewTransactionsPerAccount,
|
||||
ERC20: event.ERC20,
|
||||
MaxKnownBlocks: maxKnownBlocks,
|
||||
})
|
||||
}
|
||||
} else if event.Type == wallet.EventMaxKnownBlock {
|
||||
for _, address := range event.Accounts {
|
||||
if _, ok := maxKnownBlocks[address]; !ok {
|
||||
maxKnownBlocks[address] = event.BlockNumber
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// StopWalletWatcher - stops watching for new wallet events
|
||||
func (s *Service) StopWalletWatcher() {
|
||||
if s.walletTransmitter.quit != nil {
|
||||
close(s.walletTransmitter.quit)
|
||||
s.walletTransmitter.wg.Wait()
|
||||
s.walletTransmitter.quit = nil
|
||||
}
|
||||
}
|
||||
|
||||
// IsWatchingWallet - check if local-notifications are subscribed to wallet updates
|
||||
func (s *Service) IsWatchingWallet() bool {
|
||||
return s.walletTransmitter.quit != nil
|
||||
}
|
10
services/local-notifications/types.go
Normal file
10
services/local-notifications/types.go
Normal file
@ -0,0 +1,10 @@
|
||||
package localnotifications
|
||||
|
||||
const (
|
||||
CategoryTransaction PushCategory = "transaction"
|
||||
CategoryMessage PushCategory = "newMessage"
|
||||
CategoryCommunityRequestToJoin = "communityRequestToJoin"
|
||||
|
||||
TypeTransaction NotificationType = "transaction"
|
||||
TypeMessage NotificationType = "message"
|
||||
)
|
@ -2,11 +2,10 @@ package signal
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
|
||||
statusproto "github.com/status-im/status-go/protocol"
|
||||
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
)
|
||||
|
||||
@ -158,6 +157,6 @@ func SendWhisperFilterAdded(filters []*Filter) {
|
||||
send(EventWhisperFilterAdded, WhisperFilterAddedSignal{Filters: filters})
|
||||
}
|
||||
|
||||
func SendNewMessages(response *statusproto.MessengerResponse) {
|
||||
send(EventNewMessages, response)
|
||||
func SendNewMessages(obj json.Marshaler) {
|
||||
send(EventNewMessages, obj)
|
||||
}
|
||||
|
@ -16,7 +16,8 @@ const (
|
||||
// https://notes.status.im/i8Y_l7ccTiOYq09HVgoFwA
|
||||
kdfIterationsNumber = 3200
|
||||
// WALMode for sqlite.
|
||||
WALMode = "wal"
|
||||
WALMode = "wal"
|
||||
inMemoryPath = ":memory:"
|
||||
)
|
||||
|
||||
// DecryptDB completely removes the encryption from the db
|
||||
@ -97,7 +98,7 @@ func openDB(path, key string) (*sql.DB, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mode != WALMode {
|
||||
if mode != WALMode && path != inMemoryPath {
|
||||
return nil, fmt.Errorf("unable to set journal_mode to WAL. actual mode %s", mode)
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user