mirror of
https://github.com/status-im/status-go.git
synced 2025-02-24 20:58:26 +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/params"
|
||||||
"github.com/status-im/status-go/profiling"
|
"github.com/status-im/status-go/profiling"
|
||||||
"github.com/status-im/status-go/protocol"
|
"github.com/status-im/status-go/protocol"
|
||||||
localnotifications "github.com/status-im/status-go/services/local-notifications"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -400,13 +399,11 @@ func retrieveMessagesLoop(messenger *protocol.Messenger, tick time.Duration, can
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
mr, err := messenger.RetrieveAll()
|
_, err := messenger.RetrieveAll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("failed to retrieve raw messages", "err", err)
|
logger.Error("failed to retrieve raw messages", "err", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
localnotifications.SendMessageNotifications(mr.Notifications)
|
|
||||||
case <-cancel:
|
case <-cancel:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -530,6 +530,15 @@ func (db *Database) DeleteAccount(address types.Address) error {
|
|||||||
return err
|
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) {
|
func (db *Database) GetWalletAddress() (rst types.Address, err error) {
|
||||||
err = db.db.QueryRow("SELECT address FROM accounts WHERE wallet = 1").Scan(&rst)
|
err = db.db.QueryRow("SELECT address FROM accounts WHERE wallet = 1").Scan(&rst)
|
||||||
return
|
return
|
||||||
|
@ -108,6 +108,14 @@ func (c *Chat) OneToOne() bool {
|
|||||||
return c.ChatType == ChatTypeOneToOne
|
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 {
|
func (c *Chat) CommunityChatID() string {
|
||||||
if c.ChatType != ChatTypeCommunityChat {
|
if c.ChatType != ChatTypeCommunityChat {
|
||||||
return c.ID
|
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 {
|
func CreateCommunityChats(org *communities.Community, timesource common.TimeSource) []*Chat {
|
||||||
var chats []*Chat
|
var chats []*Chat
|
||||||
orgID := org.IDString()
|
orgID := org.IDString()
|
||||||
|
@ -130,6 +130,7 @@ func (s *ChatTestSuite) TestSerializeJSON() {
|
|||||||
message.Clock = 1
|
message.Clock = 1
|
||||||
message.Text = "`some markdown text`"
|
message.Text = "`some markdown text`"
|
||||||
s.Require().NoError(message.PrepareContent())
|
s.Require().NoError(message.PrepareContent())
|
||||||
|
message.ParsedTextAst = nil
|
||||||
chat.LastMessage = message
|
chat.LastMessage = message
|
||||||
|
|
||||||
encodedJSON, err := json.Marshal(chat)
|
encodedJSON, err := json.Marshal(chat)
|
||||||
|
@ -110,6 +110,8 @@ type Message struct {
|
|||||||
RTL bool `json:"rtl"`
|
RTL bool `json:"rtl"`
|
||||||
// ParsedText is the parsed markdown for displaying
|
// ParsedText is the parsed markdown for displaying
|
||||||
ParsedText []byte `json:"parsedText,omitempty"`
|
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 is the count of newlines in the message
|
||||||
LineCount int `json:"lineCount"`
|
LineCount int `json:"lineCount"`
|
||||||
// Base64Image is the converted base64 image
|
// 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
|
// 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
|
mentions []string
|
||||||
links []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
|
// only on entering we fetch, otherwise we go on
|
||||||
if !entering {
|
if !entering {
|
||||||
return ast.GoToNext
|
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) {
|
func extractMentionsAndLinks(parsedText ast.Node) ([]string, []string) {
|
||||||
visitor := &NodeVisitor{}
|
visitor := &MentionsAndLinksVisitor{}
|
||||||
ast.Walk(parsedText, visitor)
|
ast.Walk(parsedText, visitor)
|
||||||
return visitor.mentions, visitor.links
|
return visitor.mentions, visitor.links
|
||||||
}
|
}
|
||||||
@ -358,6 +400,7 @@ func (m *Message) PrepareContent() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
m.ParsedTextAst = &parsedText
|
||||||
m.ParsedText = jsonParsedText
|
m.ParsedText = jsonParsedText
|
||||||
m.LineCount = strings.Count(m.Text, "\n")
|
m.LineCount = strings.Count(m.Text, "\n")
|
||||||
m.RTL = isRTL(m.Text)
|
m.RTL = isRTL(m.Text)
|
||||||
@ -367,6 +410,34 @@ func (m *Message) PrepareContent() error {
|
|||||||
return m.parseAudio()
|
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) {
|
func getAudioMessageMIME(i *protobuf.AudioMessage) (string, error) {
|
||||||
switch i.Type {
|
switch i.Type {
|
||||||
case protobuf.AudioMessage_AAC:
|
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[0], link1)
|
||||||
require.Equal(t, message.Links[1], link2)
|
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.Members = o.config.CommunityDescription.Members
|
||||||
communityItem.Permissions = o.config.CommunityDescription.Permissions
|
communityItem.Permissions = o.config.CommunityDescription.Permissions
|
||||||
if o.config.CommunityDescription.Identity != nil {
|
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.Color = o.config.CommunityDescription.Identity.Color
|
||||||
communityItem.Description = o.config.CommunityDescription.Identity.Description
|
communityItem.Description = o.config.CommunityDescription.Identity.Description
|
||||||
for t, i := range o.config.CommunityDescription.Identity.Images {
|
for t, i := range o.config.CommunityDescription.Identity.Images {
|
||||||
@ -132,6 +132,10 @@ func (o *Community) MarshalJSON() ([]byte, error) {
|
|||||||
return json.Marshal(communityItem)
|
return json.Marshal(communityItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *Community) Name() string {
|
||||||
|
return o.config.CommunityDescription.Identity.DisplayName
|
||||||
|
}
|
||||||
|
|
||||||
func (o *Community) initialize() {
|
func (o *Community) initialize() {
|
||||||
if o.config.CommunityDescription == nil {
|
if o.config.CommunityDescription == nil {
|
||||||
o.config.CommunityDescription = &protobuf.CommunityDescription{}
|
o.config.CommunityDescription = &protobuf.CommunityDescription{}
|
||||||
@ -339,19 +343,22 @@ func (o *Community) isBanned(pk *ecdsa.PublicKey) bool {
|
|||||||
return false
|
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)
|
member := o.getMember(pk)
|
||||||
if member == nil {
|
if member == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, r := range member.Roles {
|
return o.hasMemberPermission(member, roles)
|
||||||
if r == role {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Community) HasMember(pk *ecdsa.PublicKey) bool {
|
func (o *Community) HasMember(pk *ecdsa.PublicKey) bool {
|
||||||
@ -605,6 +612,22 @@ func (o *Community) IsAdmin() bool {
|
|||||||
return o.config.PrivateKey != nil
|
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 {
|
func (o *Community) validateRequestToJoinWithChatID(request *protobuf.CommunityRequestToJoin) error {
|
||||||
|
|
||||||
chat, ok := o.config.CommunityDescription.Chats[request.ChatId]
|
chat, ok := o.config.CommunityDescription.Chats[request.ChatId]
|
||||||
@ -901,7 +924,8 @@ func (o *Community) CanManageUsers(pk *ecdsa.PublicKey) bool {
|
|||||||
return false
|
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 {
|
func (o *Community) isMember() bool {
|
||||||
@ -933,6 +957,23 @@ func (o *Community) nextClock() uint64 {
|
|||||||
return o.config.CommunityDescription.Clock + 1
|
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 {
|
func emptyCommunityChanges() *CommunityChanges {
|
||||||
return &CommunityChanges{
|
return &CommunityChanges{
|
||||||
MembersAdded: make(map[string]*protobuf.CommunityMember),
|
MembersAdded: make(map[string]*protobuf.CommunityMember),
|
||||||
|
@ -309,6 +309,8 @@ func (s *MessengerCommunitiesSuite) TestInviteUsersToCommunity() {
|
|||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
s.Require().NotNil(response)
|
s.Require().NotNil(response)
|
||||||
s.Require().Len(response.Communities(), 1)
|
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]
|
community := response.Communities()[0]
|
||||||
|
|
||||||
@ -787,6 +789,12 @@ func (s *MessengerCommunitiesSuite) TestRequestAccessAgain() {
|
|||||||
|
|
||||||
s.Require().Equal(requestToJoin1.ID, requestToJoin2.ID)
|
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
|
// Accept request
|
||||||
|
|
||||||
acceptRequestToJoin := &requests.AcceptRequestToJoinCommunity{ID: requestToJoin1.ID}
|
acceptRequestToJoin := &requests.AcceptRequestToJoinCommunity{ID: requestToJoin1.ID}
|
||||||
|
@ -26,6 +26,22 @@ type ContactDeviceInfo struct {
|
|||||||
FCMToken string `json:"fcmToken"`
|
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
|
// Contact has information about a "Contact". A contact is not necessarily one
|
||||||
// that we added or added us, that's based on SystemTags.
|
// that we added or added us, that's based on SystemTags.
|
||||||
type Contact struct {
|
type Contact struct {
|
||||||
|
@ -1,9 +1,128 @@
|
|||||||
package protocol
|
package protocol
|
||||||
|
|
||||||
import "github.com/status-im/status-go/protocol/common"
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
type MessageNotificationBody struct {
|
gethcommon "github.com/ethereum/go-ethereum/common"
|
||||||
Message *common.Message `json:"message"`
|
"github.com/status-im/status-go/protocol/common"
|
||||||
Contact *Contact `json:"contact"`
|
"github.com/status-im/status-go/protocol/communities"
|
||||||
Chat *Chat `json:"chat"`
|
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)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,10 +21,12 @@ import (
|
|||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/golang/protobuf/proto"
|
"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/crypto"
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
"github.com/status-im/status-go/eth-node/types"
|
||||||
userimage "github.com/status-im/status-go/images"
|
userimage "github.com/status-im/status-go/images"
|
||||||
"github.com/status-im/status-go/multiaccounts"
|
"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/audio"
|
||||||
"github.com/status-im/status-go/protocol/common"
|
"github.com/status-im/status-go/protocol/common"
|
||||||
"github.com/status-im/status-go/protocol/communities"
|
"github.com/status-im/status-go/protocol/communities"
|
||||||
@ -99,6 +101,7 @@ type Messenger struct {
|
|||||||
mailserver []byte
|
mailserver []byte
|
||||||
database *sql.DB
|
database *sql.DB
|
||||||
multiAccounts *multiaccounts.Database
|
multiAccounts *multiaccounts.Database
|
||||||
|
settings *accounts.Database
|
||||||
account *multiaccounts.Account
|
account *multiaccounts.Account
|
||||||
mailserversDatabase *mailservers.Database
|
mailserversDatabase *mailservers.Database
|
||||||
quit chan struct{}
|
quit chan struct{}
|
||||||
@ -186,7 +189,7 @@ func NewMessenger(
|
|||||||
if c.db == nil {
|
if c.db == nil {
|
||||||
logger.Info("opening a database", zap.String("dbPath", c.dbConfig.dbPath))
|
logger.Info("opening a database", zap.String("dbPath", c.dbConfig.dbPath))
|
||||||
var err error
|
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 {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to initialize database from the db config")
|
return nil, errors.Wrap(err, "failed to initialize database from the db config")
|
||||||
}
|
}
|
||||||
@ -304,6 +307,7 @@ func NewMessenger(
|
|||||||
verifyTransactionClient: c.verifyTransactionClient,
|
verifyTransactionClient: c.verifyTransactionClient,
|
||||||
database: database,
|
database: database,
|
||||||
multiAccounts: c.multiAccount,
|
multiAccounts: c.multiAccount,
|
||||||
|
settings: accounts.NewDB(database),
|
||||||
mailserversDatabase: c.mailserversDatabase,
|
mailserversDatabase: c.mailserversDatabase,
|
||||||
account: c.account,
|
account: c.account,
|
||||||
quit: make(chan struct{}),
|
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
|
// []Response.Notifications if the message is m.New
|
||||||
func (r *ReceivedMessageState) addNewMessageNotification(publicKey ecdsa.PublicKey, m *common.Message, responseTo *common.Message) error {
|
func (r *ReceivedMessageState) addNewMessageNotification(publicKey ecdsa.PublicKey, m *common.Message, responseTo *common.Message) error {
|
||||||
if !m.New {
|
if !m.New {
|
||||||
@ -2469,17 +2473,14 @@ func (r *ReceivedMessageState) addNewMessageNotification(publicKey ecdsa.PublicK
|
|||||||
contactID := contactIDFromPublicKey(pubKey)
|
contactID := contactIDFromPublicKey(pubKey)
|
||||||
|
|
||||||
chat := r.AllChats[m.LocalChatID]
|
chat := r.AllChats[m.LocalChatID]
|
||||||
notification := MessageNotificationBody{
|
contact := r.AllContacts[contactID]
|
||||||
Message: m,
|
|
||||||
Contact: r.AllContacts[contactID],
|
|
||||||
Chat: chat,
|
|
||||||
}
|
|
||||||
|
|
||||||
if showNotification(publicKey, notification, responseTo) {
|
if showMessageNotification(publicKey, m, chat, responseTo) {
|
||||||
r.Response.Notifications = append(
|
notification, err := NewMessageNotification(m.ID, m, chat, contact, r.AllContacts)
|
||||||
r.Response.Notifications,
|
if err != nil {
|
||||||
notification,
|
return err
|
||||||
)
|
}
|
||||||
|
r.Response.AddNotification(notification)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -3009,13 +3010,19 @@ func (m *Messenger) handleRetrievedMessages(chatWithMessages map[transport.Filte
|
|||||||
}
|
}
|
||||||
messageState.Response.Messages = messagesWithResponses
|
messageState.Response.Messages = messagesWithResponses
|
||||||
|
|
||||||
|
notificationsEnabled, err := m.settings.GetNotificationsEnabled()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
for _, message := range messageState.Response.Messages {
|
for _, message := range messageState.Response.Messages {
|
||||||
if _, ok := newMessagesIds[message.ID]; ok {
|
if _, ok := newMessagesIds[message.ID]; ok {
|
||||||
message.New = true
|
message.New = true
|
||||||
|
|
||||||
// Create notification body to be eventually passed to `localnotifications.SendMessageNotifications()`
|
if notificationsEnabled {
|
||||||
if err = messageState.addNewMessageNotification(m.identity.PublicKey, message, messagesByID[message.ResponseTo]); err != nil {
|
// Create notification body to be eventually passed to `localnotifications.SendMessageNotifications()`
|
||||||
return nil, err
|
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
|
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
|
// SetMailserver sets the currently used mailserver
|
||||||
func (m *Messenger) SetMailserver(peer []byte) {
|
func (m *Messenger) SetMailserver(peer []byte) {
|
||||||
m.mailserver = peer
|
m.mailserver = peer
|
||||||
@ -3941,11 +3924,18 @@ func (m *Messenger) ValidateTransactions(ctx context.Context, addresses []types.
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
response.Notifications = append(response.Notifications, MessageNotificationBody{
|
|
||||||
Message: message,
|
notificationsEnabled, err := m.settings.GetNotificationsEnabled()
|
||||||
Contact: contact,
|
if err != nil {
|
||||||
Chat: chat,
|
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 := &MessengerResponse{RequestsToJoinCommunity: []*communities.RequestToJoin{requestToJoin}}
|
||||||
response.AddCommunity(community)
|
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
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -314,7 +333,7 @@ func (m *Messenger) CreateCommunity(request *requests.CreateCommunity) (*Messeng
|
|||||||
}
|
}
|
||||||
|
|
||||||
description.Members = make(map[string]*protobuf.CommunityMember)
|
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)
|
community, err := m.communitiesManager.CreateCommunity(description)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/status-im/status-go/protocol/communities"
|
"github.com/status-im/status-go/protocol/communities"
|
||||||
"github.com/status-im/status-go/protocol/encryption/multidevice"
|
"github.com/status-im/status-go/protocol/encryption/multidevice"
|
||||||
"github.com/status-im/status-go/protocol/transport"
|
"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"
|
"github.com/status-im/status-go/services/mailservers"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -23,12 +24,13 @@ type MessengerResponse struct {
|
|||||||
Mailservers []mailservers.Mailserver
|
Mailservers []mailservers.Mailserver
|
||||||
MailserverTopics []mailservers.MailserverTopic
|
MailserverTopics []mailservers.MailserverTopic
|
||||||
MailserverRanges []mailservers.ChatRequestRange
|
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
|
// notifications a list of notifications derived from messenger events
|
||||||
removedChats map[string]bool
|
// that are useful to notify the user about
|
||||||
communities map[string]*communities.Community
|
notifications map[string]*localnotifications.Notification
|
||||||
|
chats map[string]*Chat
|
||||||
|
removedChats map[string]bool
|
||||||
|
communities map[string]*communities.Community
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *MessengerResponse) MarshalJSON() ([]byte, error) {
|
func (r *MessengerResponse) MarshalJSON() ([]byte, error) {
|
||||||
@ -47,9 +49,10 @@ func (r *MessengerResponse) MarshalJSON() ([]byte, error) {
|
|||||||
Mailservers []mailservers.Mailserver `json:"mailservers,omitempty"`
|
Mailservers []mailservers.Mailserver `json:"mailservers,omitempty"`
|
||||||
MailserverTopics []mailservers.MailserverTopic `json:"mailserverTopics,omitempty"`
|
MailserverTopics []mailservers.MailserverTopic `json:"mailserverTopics,omitempty"`
|
||||||
MailserverRanges []mailservers.ChatRequestRange `json:"mailserverRanges,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 a list of notifications derived from messenger events
|
||||||
Notifications []MessageNotificationBody `json:"notifications"`
|
// that are useful to notify the user about
|
||||||
Communities []*communities.Community `json:"communities,omitempty"`
|
Notifications []*localnotifications.Notification `json:"notifications"`
|
||||||
|
Communities []*communities.Community `json:"communities,omitempty"`
|
||||||
}{
|
}{
|
||||||
Messages: r.Messages,
|
Messages: r.Messages,
|
||||||
Contacts: r.Contacts,
|
Contacts: r.Contacts,
|
||||||
@ -63,9 +66,9 @@ func (r *MessengerResponse) MarshalJSON() ([]byte, error) {
|
|||||||
Mailservers: r.Mailservers,
|
Mailservers: r.Mailservers,
|
||||||
MailserverTopics: r.MailserverTopics,
|
MailserverTopics: r.MailserverTopics,
|
||||||
MailserverRanges: r.MailserverRanges,
|
MailserverRanges: r.MailserverRanges,
|
||||||
Notifications: r.Notifications,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
responseItem.Notifications = r.Notifications()
|
||||||
responseItem.Chats = r.Chats()
|
responseItem.Chats = r.Chats()
|
||||||
responseItem.Communities = r.Communities()
|
responseItem.Communities = r.Communities()
|
||||||
responseItem.RemovedChats = r.RemovedChats()
|
responseItem.RemovedChats = r.RemovedChats()
|
||||||
@ -97,6 +100,14 @@ func (r *MessengerResponse) Communities() []*communities.Community {
|
|||||||
return communities
|
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 {
|
func (r *MessengerResponse) IsEmpty() bool {
|
||||||
return len(r.chats)+
|
return len(r.chats)+
|
||||||
len(r.Messages)+
|
len(r.Messages)+
|
||||||
@ -112,7 +123,7 @@ func (r *MessengerResponse) IsEmpty() bool {
|
|||||||
len(r.MailserverTopics)+
|
len(r.MailserverTopics)+
|
||||||
len(r.Mailservers)+
|
len(r.Mailservers)+
|
||||||
len(r.MailserverRanges)+
|
len(r.MailserverRanges)+
|
||||||
len(r.Notifications)+
|
len(r.notifications)+
|
||||||
len(r.RequestsToJoinCommunity) == 0
|
len(r.RequestsToJoinCommunity) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,7 +138,6 @@ func (r *MessengerResponse) Merge(response *MessengerResponse) error {
|
|||||||
len(response.Mailservers)+
|
len(response.Mailservers)+
|
||||||
len(response.MailserverTopics)+
|
len(response.MailserverTopics)+
|
||||||
len(response.MailserverRanges)+
|
len(response.MailserverRanges)+
|
||||||
len(response.Notifications)+
|
|
||||||
len(response.EmojiReactions)+
|
len(response.EmojiReactions)+
|
||||||
len(response.CommunityChanges) != 0 {
|
len(response.CommunityChanges) != 0 {
|
||||||
return ErrNotImplemented
|
return ErrNotImplemented
|
||||||
@ -135,6 +145,7 @@ func (r *MessengerResponse) Merge(response *MessengerResponse) error {
|
|||||||
|
|
||||||
r.AddChats(response.Chats())
|
r.AddChats(response.Chats())
|
||||||
r.AddRemovedChats(response.RemovedChats())
|
r.AddRemovedChats(response.RemovedChats())
|
||||||
|
r.AddNotifications(response.Notifications())
|
||||||
r.overrideMessages(response.Messages)
|
r.overrideMessages(response.Messages)
|
||||||
r.overrideFilters(response.Filters)
|
r.overrideFilters(response.Filters)
|
||||||
r.overrideRemovedFilters(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) {
|
func (r *MessengerResponse) AddRemovedChats(chats []string) {
|
||||||
for _, c := range chats {
|
for _, c := range chats {
|
||||||
r.AddRemovedChat(c)
|
r.AddRemovedChat(c)
|
||||||
|
@ -88,18 +88,21 @@ const (
|
|||||||
PushNotification_UNKNOWN_PUSH_NOTIFICATION_TYPE PushNotification_PushNotificationType = 0
|
PushNotification_UNKNOWN_PUSH_NOTIFICATION_TYPE PushNotification_PushNotificationType = 0
|
||||||
PushNotification_MESSAGE PushNotification_PushNotificationType = 1
|
PushNotification_MESSAGE PushNotification_PushNotificationType = 1
|
||||||
PushNotification_MENTION PushNotification_PushNotificationType = 2
|
PushNotification_MENTION PushNotification_PushNotificationType = 2
|
||||||
|
PushNotification_REQUEST_TO_JOIN_COMMUNITY PushNotification_PushNotificationType = 3
|
||||||
)
|
)
|
||||||
|
|
||||||
var PushNotification_PushNotificationType_name = map[int32]string{
|
var PushNotification_PushNotificationType_name = map[int32]string{
|
||||||
0: "UNKNOWN_PUSH_NOTIFICATION_TYPE",
|
0: "UNKNOWN_PUSH_NOTIFICATION_TYPE",
|
||||||
1: "MESSAGE",
|
1: "MESSAGE",
|
||||||
2: "MENTION",
|
2: "MENTION",
|
||||||
|
3: "REQUEST_TO_JOIN_COMMUNITY",
|
||||||
}
|
}
|
||||||
|
|
||||||
var PushNotification_PushNotificationType_value = map[string]int32{
|
var PushNotification_PushNotificationType_value = map[string]int32{
|
||||||
"UNKNOWN_PUSH_NOTIFICATION_TYPE": 0,
|
"UNKNOWN_PUSH_NOTIFICATION_TYPE": 0,
|
||||||
"MESSAGE": 1,
|
"MESSAGE": 1,
|
||||||
"MENTION": 2,
|
"MENTION": 2,
|
||||||
|
"REQUEST_TO_JOIN_COMMUNITY": 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x PushNotification_PushNotificationType) String() string {
|
func (x PushNotification_PushNotificationType) String() string {
|
||||||
@ -833,70 +836,72 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var fileDescriptor_200acd86044eaa5d = []byte{
|
var fileDescriptor_200acd86044eaa5d = []byte{
|
||||||
// 1038 bytes of a gzipped FileDescriptorProto
|
// 1064 bytes of a gzipped FileDescriptorProto
|
||||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x55, 0xdd, 0x6e, 0xe3, 0x44,
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x55, 0x4d, 0x6f, 0xe3, 0xc4,
|
||||||
0x14, 0xc6, 0x4e, 0xda, 0x24, 0x27, 0x69, 0x9a, 0x0e, 0x6d, 0x6a, 0x0a, 0x5d, 0x82, 0x01, 0x11,
|
0x1b, 0xff, 0x3b, 0x49, 0x9b, 0xe4, 0x49, 0x9a, 0x7a, 0xe7, 0xdf, 0x4d, 0xbd, 0x85, 0x2e, 0xc1,
|
||||||
0xf5, 0xa2, 0xa0, 0x22, 0xb1, 0x2b, 0xf6, 0x86, 0x90, 0xba, 0x5d, 0xab, 0x8d, 0x1d, 0x26, 0x2e,
|
0x80, 0x88, 0x7a, 0x28, 0xa8, 0x48, 0xec, 0x8a, 0xbd, 0x10, 0x52, 0xb7, 0x6b, 0xda, 0xd8, 0xd9,
|
||||||
0xab, 0x95, 0x90, 0x2c, 0xd7, 0x9e, 0xb6, 0x56, 0x53, 0x8f, 0xf1, 0x4c, 0x8a, 0x72, 0xc7, 0x03,
|
0x89, 0xc3, 0xaa, 0x12, 0xd2, 0xc8, 0xb5, 0xa7, 0xad, 0xd5, 0xd4, 0x36, 0x9e, 0x49, 0x51, 0x6e,
|
||||||
0x70, 0xc3, 0x2d, 0x57, 0x3c, 0xc3, 0xbe, 0x12, 0x2f, 0x82, 0x3c, 0x1e, 0xa7, 0x6e, 0x93, 0xa6,
|
0x88, 0x33, 0x17, 0xae, 0x9c, 0xf8, 0x0c, 0xfb, 0x09, 0x91, 0xc7, 0xe3, 0xd4, 0x6d, 0xd2, 0x17,
|
||||||
0x45, 0xe2, 0x2a, 0x39, 0xdf, 0xf9, 0x99, 0xf3, 0xfb, 0x19, 0xb4, 0x78, 0xc2, 0xae, 0xdc, 0x88,
|
0x24, 0x4e, 0xc9, 0xf3, 0x7b, 0x5e, 0xe6, 0x79, 0xfd, 0x19, 0xb4, 0x78, 0xca, 0x2e, 0x48, 0x18,
|
||||||
0xf2, 0xf0, 0x22, 0xf4, 0x3d, 0x1e, 0xd2, 0x88, 0xed, 0xc7, 0x09, 0xe5, 0x14, 0x55, 0xc5, 0xcf,
|
0xf1, 0xe0, 0x2c, 0xf0, 0x5c, 0x1e, 0x44, 0x21, 0xdb, 0x8d, 0x93, 0x88, 0x47, 0xa8, 0x26, 0x7e,
|
||||||
0xf9, 0xe4, 0x62, 0xe7, 0x43, 0xff, 0xca, 0xe3, 0x6e, 0x18, 0x90, 0x88, 0x87, 0x7c, 0x9a, 0xa9,
|
0x4e, 0xa7, 0x67, 0x5b, 0xff, 0xf7, 0x2e, 0x5c, 0x4e, 0x02, 0x9f, 0x86, 0x3c, 0xe0, 0xb3, 0x4c,
|
||||||
0xf5, 0xbf, 0x57, 0xe0, 0x93, 0xe1, 0x84, 0x5d, 0x59, 0x05, 0x57, 0x4c, 0x2e, 0x43, 0xc6, 0x13,
|
0xad, 0xff, 0xbd, 0x02, 0x1f, 0x0f, 0xa7, 0xec, 0xc2, 0x2a, 0xb8, 0x62, 0x7a, 0x1e, 0x30, 0x9e,
|
||||||
0xf1, 0x1f, 0xd9, 0x00, 0x9c, 0x5e, 0x93, 0xc8, 0xe5, 0xd3, 0x98, 0x68, 0x4a, 0x47, 0xe9, 0x36,
|
0x88, 0xff, 0xc8, 0x06, 0xe0, 0xd1, 0x25, 0x0d, 0x09, 0x9f, 0xc5, 0x54, 0x53, 0x3a, 0x4a, 0xb7,
|
||||||
0x0f, 0xbe, 0xd9, 0xcf, 0x83, 0xee, 0x2f, 0xf3, 0xdd, 0x77, 0x52, 0x47, 0x67, 0x1a, 0x13, 0x5c,
|
0xb5, 0xf7, 0xf5, 0x6e, 0x1e, 0x74, 0xf7, 0x21, 0xdf, 0x5d, 0x27, 0x75, 0x74, 0x66, 0x31, 0xc5,
|
||||||
0xe3, 0xf9, 0x5f, 0xf4, 0x19, 0x34, 0x02, 0x72, 0x1b, 0xfa, 0xc4, 0x15, 0x98, 0xa6, 0x76, 0x94,
|
0x75, 0x9e, 0xff, 0x45, 0x9f, 0x42, 0xd3, 0xa7, 0xd7, 0x81, 0x47, 0x89, 0xc0, 0xb4, 0x52, 0x47,
|
||||||
0x6e, 0x0d, 0xd7, 0x33, 0x4c, 0x78, 0xa0, 0xaf, 0x60, 0x3d, 0x8c, 0x18, 0xf7, 0xc6, 0x63, 0x11,
|
0xe9, 0xd6, 0x71, 0x23, 0xc3, 0x84, 0x07, 0xfa, 0x12, 0xd6, 0x83, 0x90, 0x71, 0x77, 0x32, 0x11,
|
||||||
0xc7, 0x0d, 0x03, 0xad, 0x24, 0xac, 0x9a, 0x45, 0xd8, 0x0c, 0xd2, 0x58, 0x9e, 0xef, 0x13, 0xc6,
|
0x71, 0x48, 0xe0, 0x6b, 0x65, 0x61, 0xd5, 0x2a, 0xc2, 0xa6, 0x9f, 0xc6, 0x72, 0x3d, 0x8f, 0x32,
|
||||||
0x64, 0xac, 0x72, 0x16, 0x2b, 0xc3, 0xb2, 0x58, 0x1a, 0x54, 0x48, 0xe4, 0x9d, 0x8f, 0x49, 0xa0,
|
0x26, 0x63, 0x55, 0xb2, 0x58, 0x19, 0x96, 0xc5, 0xd2, 0xa0, 0x4a, 0x43, 0xf7, 0x74, 0x42, 0x7d,
|
||||||
0xad, 0x74, 0x94, 0x6e, 0x15, 0xe7, 0x62, 0xaa, 0xb9, 0x25, 0x09, 0x0b, 0x69, 0xa4, 0xad, 0x76,
|
0x6d, 0xa5, 0xa3, 0x74, 0x6b, 0x38, 0x17, 0x53, 0xcd, 0x35, 0x4d, 0x58, 0x10, 0x85, 0xda, 0x6a,
|
||||||
0x94, 0x6e, 0x19, 0xe7, 0x22, 0xea, 0x42, 0xcb, 0x1b, 0x8f, 0xe9, 0x6f, 0x24, 0x70, 0xaf, 0xc9,
|
0x47, 0xe9, 0x56, 0x70, 0x2e, 0xa2, 0x2e, 0xa8, 0xee, 0x64, 0x12, 0xfd, 0x4a, 0x7d, 0x72, 0x49,
|
||||||
0xd4, 0x1d, 0x87, 0x8c, 0x6b, 0x95, 0x4e, 0xa9, 0xdb, 0xc0, 0x4d, 0x89, 0x9f, 0x90, 0xe9, 0x69,
|
0x67, 0x64, 0x12, 0x30, 0xae, 0x55, 0x3b, 0xe5, 0x6e, 0x13, 0xb7, 0x24, 0x7e, 0x44, 0x67, 0xc7,
|
||||||
0xc8, 0x38, 0xda, 0x83, 0x8d, 0xf3, 0x31, 0xf5, 0xaf, 0x49, 0xe0, 0x8a, 0xee, 0x0a, 0xd3, 0xaa,
|
0x01, 0xe3, 0x68, 0x07, 0x9e, 0x9d, 0x4e, 0x22, 0xef, 0x92, 0xfa, 0x44, 0x74, 0x57, 0x98, 0xd6,
|
||||||
0x30, 0x5d, 0x97, 0x8a, 0xfe, 0x95, 0xc7, 0x85, 0xed, 0x0b, 0x80, 0x49, 0x94, 0x88, 0xfe, 0x90,
|
0x84, 0xe9, 0xba, 0x54, 0xf4, 0x2f, 0x5c, 0x2e, 0x6c, 0x5f, 0x02, 0x4c, 0xc3, 0x44, 0xf4, 0x87,
|
||||||
0x44, 0xab, 0x89, 0x64, 0x0a, 0x08, 0xda, 0x84, 0x95, 0xcb, 0xc4, 0x8b, 0xb8, 0x06, 0x1d, 0xa5,
|
0x26, 0x5a, 0x5d, 0x24, 0x53, 0x40, 0xd0, 0x06, 0xac, 0x9c, 0x27, 0x6e, 0xc8, 0x35, 0xe8, 0x28,
|
||||||
0xdb, 0xc0, 0x99, 0x80, 0x5e, 0x82, 0x26, 0xde, 0x74, 0x2f, 0x12, 0x7a, 0xe3, 0xfa, 0x34, 0xe2,
|
0xdd, 0x26, 0xce, 0x04, 0xf4, 0x0a, 0x34, 0xf1, 0x26, 0x39, 0x4b, 0xa2, 0x2b, 0xe2, 0x45, 0x21,
|
||||||
0x9e, 0xcf, 0x99, 0x4b, 0xa3, 0xf1, 0x54, 0xab, 0x8b, 0x18, 0x5b, 0x42, 0x7f, 0x94, 0xd0, 0x9b,
|
0x77, 0x3d, 0xce, 0x48, 0x14, 0x4e, 0x66, 0x5a, 0x43, 0xc4, 0x78, 0x2e, 0xf4, 0x07, 0x49, 0x74,
|
||||||
0xbe, 0xd4, 0xda, 0xd1, 0x78, 0x8a, 0x3e, 0x86, 0x9a, 0x17, 0x47, 0x2e, 0xa7, 0x71, 0xe8, 0x6b,
|
0xd5, 0x97, 0x5a, 0x3b, 0x9c, 0xcc, 0xd0, 0x47, 0x50, 0x77, 0xe3, 0x90, 0xf0, 0x28, 0x0e, 0x3c,
|
||||||
0x0d, 0xd1, 0x98, 0xaa, 0x17, 0x47, 0x4e, 0x2a, 0xa3, 0x2f, 0xa1, 0x29, 0xd2, 0x73, 0x6f, 0xd2,
|
0xad, 0x29, 0x1a, 0x53, 0x73, 0xe3, 0xd0, 0x49, 0x65, 0xf4, 0x05, 0xb4, 0x44, 0x7a, 0xe4, 0x2a,
|
||||||
0x6d, 0xa0, 0x11, 0xd3, 0xd6, 0x44, 0xac, 0x35, 0x81, 0x0e, 0x24, 0x88, 0x5e, 0xc3, 0x4e, 0xde,
|
0xdd, 0x86, 0x28, 0x64, 0xda, 0x9a, 0x88, 0xb5, 0x26, 0xd0, 0x81, 0x04, 0xd1, 0x1b, 0xd8, 0xca,
|
||||||
0x88, 0xdc, 0xb0, 0x50, 0x67, 0x53, 0xd4, 0xb9, 0x2d, 0x2d, 0x72, 0xa7, 0xbc, 0x5e, 0xfd, 0x08,
|
0x1b, 0x91, 0x1b, 0x16, 0xea, 0x6c, 0x89, 0x3a, 0x37, 0xa5, 0x45, 0xee, 0x94, 0xd7, 0xab, 0x1f,
|
||||||
0x6a, 0xb3, 0x05, 0x40, 0x6d, 0x40, 0x67, 0xd6, 0x89, 0x65, 0xbf, 0xb5, 0x5c, 0xc7, 0x3e, 0x31,
|
0x40, 0x7d, 0xbe, 0x00, 0xa8, 0x0d, 0x68, 0x6c, 0x1d, 0x59, 0xf6, 0x7b, 0x8b, 0x38, 0xf6, 0x91,
|
||||||
0x2c, 0xd7, 0x79, 0x37, 0x34, 0x5a, 0x1f, 0xa0, 0x35, 0xa8, 0xf5, 0x86, 0x12, 0x6b, 0x29, 0x08,
|
0x61, 0x11, 0xe7, 0x64, 0x68, 0xa8, 0xff, 0x43, 0x6b, 0x50, 0xef, 0x0d, 0x25, 0xa6, 0x2a, 0x08,
|
||||||
0x41, 0xf3, 0xc8, 0xc4, 0xc6, 0x8f, 0xbd, 0x91, 0x21, 0x31, 0x55, 0x7f, 0xaf, 0xc2, 0x17, 0xcb,
|
0x41, 0xeb, 0xc0, 0xc4, 0xc6, 0x0f, 0xbd, 0x91, 0x21, 0xb1, 0x92, 0xfe, 0xa1, 0x04, 0x9f, 0x3f,
|
||||||
0xd6, 0x0c, 0x13, 0x16, 0xd3, 0x88, 0x91, 0x74, 0xa0, 0x6c, 0x22, 0x46, 0x2f, 0xf6, 0xb4, 0x8a,
|
0xb4, 0x66, 0x98, 0xb2, 0x38, 0x0a, 0x19, 0x4d, 0x07, 0xca, 0xa6, 0x62, 0xf4, 0x62, 0x4f, 0x6b,
|
||||||
0x73, 0x11, 0x59, 0xb0, 0x42, 0x92, 0x84, 0x26, 0x62, 0xd9, 0x9a, 0x07, 0xaf, 0x9e, 0xb7, 0xbf,
|
0x38, 0x17, 0x91, 0x05, 0x2b, 0x34, 0x49, 0xa2, 0x44, 0x2c, 0x5b, 0x6b, 0xef, 0xf5, 0xd3, 0xf6,
|
||||||
0x79, 0xe0, 0x7d, 0x23, 0xf5, 0x15, 0x7b, 0x9c, 0x85, 0x41, 0xbb, 0x00, 0x09, 0xf9, 0x75, 0x42,
|
0x37, 0x0f, 0xbc, 0x6b, 0xa4, 0xbe, 0x62, 0x8f, 0xb3, 0x30, 0x68, 0x1b, 0x20, 0xa1, 0xbf, 0x4c,
|
||||||
0x18, 0xcf, 0x77, 0xb3, 0x81, 0x6b, 0x12, 0x31, 0x03, 0xfd, 0x77, 0x05, 0x6a, 0x33, 0x9f, 0x62,
|
0x29, 0xe3, 0xf9, 0x6e, 0x36, 0x71, 0x5d, 0x22, 0xa6, 0xaf, 0xff, 0xa6, 0x40, 0x7d, 0xee, 0x53,
|
||||||
0xe9, 0x06, 0xc6, 0x36, 0xce, 0x4b, 0xdf, 0x82, 0x8d, 0x41, 0xef, 0xf4, 0xc8, 0xc6, 0x03, 0xe3,
|
0x2c, 0xdd, 0xc0, 0xd8, 0xc6, 0x79, 0xe9, 0xcf, 0xe1, 0xd9, 0xa0, 0x77, 0x7c, 0x60, 0xe3, 0x81,
|
||||||
0xd0, 0x1d, 0x18, 0xa3, 0x51, 0xef, 0xd8, 0x68, 0x29, 0x68, 0x13, 0x5a, 0x3f, 0x1b, 0x78, 0x64,
|
0xb1, 0x4f, 0x06, 0xc6, 0x68, 0xd4, 0x3b, 0x34, 0x54, 0x05, 0x6d, 0x80, 0xfa, 0x93, 0x81, 0x47,
|
||||||
0xda, 0x96, 0x3b, 0x30, 0x47, 0x83, 0x9e, 0xd3, 0x7f, 0xd3, 0x52, 0xd1, 0x0e, 0xb4, 0xcf, 0xac,
|
0xa6, 0x6d, 0x91, 0x81, 0x39, 0x1a, 0xf4, 0x9c, 0xfe, 0x5b, 0xb5, 0x84, 0xb6, 0xa0, 0x3d, 0xb6,
|
||||||
0xd1, 0xd9, 0x70, 0x68, 0x63, 0xc7, 0x38, 0x2c, 0xf6, 0xb0, 0x94, 0x36, 0xcd, 0xb4, 0x1c, 0x03,
|
0x46, 0xe3, 0xe1, 0xd0, 0xc6, 0x8e, 0xb1, 0x5f, 0xec, 0x61, 0x39, 0x6d, 0x9a, 0x69, 0x39, 0x06,
|
||||||
0x5b, 0xbd, 0xd3, 0xec, 0x85, 0x56, 0x59, 0x7f, 0xaf, 0x80, 0x26, 0xd7, 0xa1, 0x4f, 0x03, 0xd2,
|
0xb6, 0x7a, 0xc7, 0xd9, 0x0b, 0x6a, 0x45, 0xff, 0xa0, 0x80, 0x26, 0xd7, 0xa1, 0x1f, 0xf9, 0xb4,
|
||||||
0x0b, 0x6e, 0x49, 0xc2, 0x43, 0x46, 0xd2, 0x31, 0xa2, 0x77, 0xd0, 0x9e, 0xe3, 0x0b, 0x37, 0x8c,
|
0xe7, 0x5f, 0xd3, 0x84, 0x07, 0x8c, 0xa6, 0x63, 0x44, 0x27, 0xd0, 0x5e, 0xe0, 0x0b, 0x12, 0x84,
|
||||||
0x2e, 0xa8, 0xa6, 0x74, 0x4a, 0xdd, 0xfa, 0xc1, 0xe7, 0x8f, 0xf7, 0xe7, 0xa7, 0x09, 0x49, 0xa6,
|
0x67, 0x91, 0xa6, 0x74, 0xca, 0xdd, 0xc6, 0xde, 0x67, 0xf7, 0xf7, 0xe7, 0xdd, 0x94, 0x26, 0x33,
|
||||||
0x66, 0x74, 0x41, 0xf1, 0x66, 0xfc, 0x40, 0x95, 0xa2, 0xe8, 0x35, 0xac, 0xdd, 0xa3, 0x19, 0xd1,
|
0x33, 0x3c, 0x8b, 0xf0, 0x46, 0x7c, 0x47, 0x95, 0xa2, 0xe8, 0x0d, 0xac, 0xdd, 0xa2, 0x19, 0xd1,
|
||||||
0xf1, 0xfa, 0x41, 0xfb, 0x2e, 0x62, 0xba, 0x1f, 0xa6, 0xd4, 0xe2, 0x86, 0x5f, 0x90, 0xf4, 0x57,
|
0xf1, 0xc6, 0x5e, 0xfb, 0x26, 0x62, 0xba, 0x1f, 0xa6, 0xd4, 0xe2, 0xa6, 0x57, 0x90, 0xf4, 0xd7,
|
||||||
0xb0, 0xb5, 0xf0, 0x3d, 0xf4, 0x29, 0xd4, 0xe3, 0xc9, 0xf9, 0x38, 0xf4, 0xd3, 0x7b, 0x64, 0x22,
|
0xf0, 0x7c, 0xe9, 0x7b, 0xe8, 0x13, 0x68, 0xc4, 0xd3, 0xd3, 0x49, 0xe0, 0xa5, 0xf7, 0xc8, 0x44,
|
||||||
0xcb, 0x06, 0x86, 0x0c, 0x3a, 0x21, 0x53, 0xa6, 0xff, 0xa1, 0xc2, 0x47, 0x8f, 0xa6, 0x3a, 0x47,
|
0x96, 0x4d, 0x0c, 0x19, 0x74, 0x44, 0x67, 0x4c, 0xff, 0xa3, 0x04, 0x2f, 0xee, 0x4d, 0x75, 0x81,
|
||||||
0x13, 0xca, 0x3c, 0x4d, 0x2c, 0xa0, 0x1c, 0x75, 0x21, 0xe5, 0xec, 0x02, 0xdc, 0xa5, 0x92, 0x8f,
|
0x26, 0x94, 0x45, 0x9a, 0x58, 0x42, 0x39, 0xa5, 0xa5, 0x94, 0xb3, 0x0d, 0x70, 0x93, 0x4a, 0x3e,
|
||||||
0x7e, 0x96, 0xc9, 0x42, 0xea, 0x28, 0x2f, 0xa4, 0x8e, 0xd9, 0xb9, 0xaf, 0x14, 0xcf, 0xfd, 0x71,
|
0xfa, 0x79, 0x26, 0x4b, 0xa9, 0xa3, 0xb2, 0x94, 0x3a, 0xe6, 0xe7, 0xbe, 0x52, 0x3c, 0xf7, 0xfb,
|
||||||
0x52, 0xda, 0x83, 0x0d, 0x46, 0x92, 0x5b, 0x92, 0xb8, 0x85, 0xf7, 0x2b, 0xc2, 0x77, 0x3d, 0x53,
|
0x49, 0x69, 0x07, 0x9e, 0x31, 0x9a, 0x5c, 0xd3, 0x84, 0x14, 0xde, 0xaf, 0x0a, 0xdf, 0xf5, 0x4c,
|
||||||
0x0c, 0xf3, 0x2c, 0xf4, 0x3f, 0x15, 0xd8, 0x5d, 0xd8, 0x8e, 0xd9, 0xad, 0xbc, 0x84, 0xf2, 0x7f,
|
0x31, 0xcc, 0xb3, 0xd0, 0xff, 0x54, 0x60, 0x7b, 0x69, 0x3b, 0xe6, 0xb7, 0xf2, 0x0a, 0x2a, 0xff,
|
||||||
0x1d, 0xb8, 0x70, 0x48, 0xeb, 0xbf, 0x21, 0x8c, 0x79, 0x97, 0x24, 0xef, 0x51, 0x03, 0xd7, 0x24,
|
0x76, 0xe0, 0xc2, 0x21, 0xad, 0xff, 0x8a, 0x32, 0xe6, 0x9e, 0xd3, 0xbc, 0x47, 0x4d, 0x5c, 0x97,
|
||||||
0x62, 0x06, 0xc5, 0x1b, 0x2c, 0xdd, 0xbb, 0x41, 0xfd, 0x1f, 0x15, 0x5a, 0x0f, 0x83, 0x3f, 0x67,
|
0x88, 0xe9, 0x17, 0x6f, 0xb0, 0x7c, 0xeb, 0x06, 0xf5, 0xdf, 0xcb, 0xa0, 0xde, 0x0d, 0xfe, 0x94,
|
||||||
0x32, 0xdb, 0x50, 0x91, 0x1b, 0x25, 0x5f, 0x5b, 0xcd, 0x76, 0xe6, 0xa9, 0x49, 0x2c, 0x98, 0x68,
|
0xc9, 0x6c, 0x42, 0x55, 0x6e, 0x94, 0x7c, 0x6d, 0x35, 0xdb, 0x99, 0xc7, 0x26, 0xb1, 0x64, 0xa2,
|
||||||
0x79, 0xe1, 0x44, 0x35, 0xa8, 0xc8, 0xfc, 0xe5, 0x28, 0x72, 0x11, 0xf5, 0xa1, 0x2c, 0xbe, 0x7a,
|
0x95, 0xa5, 0x13, 0xd5, 0xa0, 0x2a, 0xf3, 0x97, 0xa3, 0xc8, 0x45, 0xd4, 0x87, 0x8a, 0xf8, 0xea,
|
||||||
0xab, 0x82, 0x35, 0xbe, 0x7e, 0xbc, 0x49, 0x73, 0x80, 0x20, 0x0b, 0xe1, 0x8c, 0xda, 0xb0, 0xea,
|
0xad, 0x0a, 0xd6, 0xf8, 0xea, 0xfe, 0x26, 0x2d, 0x00, 0x82, 0x2c, 0x84, 0x33, 0x6a, 0xc3, 0xaa,
|
||||||
0x4d, 0xf8, 0x15, 0x4d, 0xe4, 0xb0, 0xa4, 0xa4, 0x3b, 0xb0, 0xb9, 0xc8, 0x0b, 0xe9, 0xf0, 0x22,
|
0x3b, 0xe5, 0x17, 0x51, 0x22, 0x87, 0x25, 0x25, 0x9d, 0xc1, 0xc6, 0x32, 0x2f, 0xa4, 0xc3, 0xcb,
|
||||||
0xa7, 0x8b, 0xe1, 0xd9, 0xe8, 0x8d, 0x6b, 0xd9, 0x8e, 0x79, 0x64, 0xf6, 0x7b, 0x4e, 0xca, 0x08,
|
0x9c, 0x2e, 0x86, 0xe3, 0xd1, 0x5b, 0x62, 0xd9, 0x8e, 0x79, 0x60, 0xf6, 0x7b, 0x4e, 0xca, 0x08,
|
||||||
0x92, 0x3a, 0xea, 0x50, 0xb9, 0x23, 0x0c, 0x21, 0x58, 0xa9, 0xba, 0xa5, 0xea, 0x31, 0x6c, 0xcf,
|
0x92, 0x3a, 0x1a, 0x50, 0xbd, 0x21, 0x0c, 0x21, 0x58, 0xa9, 0x5a, 0x2d, 0xa1, 0x6d, 0x78, 0x81,
|
||||||
0x53, 0x9a, 0xe0, 0x25, 0xf4, 0x1d, 0x54, 0x25, 0x45, 0x31, 0x39, 0xf6, 0x9d, 0x25, 0x3c, 0x38,
|
0x8d, 0x77, 0x63, 0x63, 0xe4, 0x10, 0xc7, 0x26, 0x3f, 0xda, 0xa6, 0x45, 0xfa, 0xf6, 0x60, 0x30,
|
||||||
0xb3, 0x7d, 0x62, 0xe2, 0xfa, 0x5f, 0x2a, 0xb4, 0xe7, 0x9f, 0x8c, 0x69, 0xc2, 0x97, 0x10, 0xf2,
|
0xb6, 0x4c, 0xe7, 0x44, 0x2d, 0xeb, 0x31, 0x6c, 0x2e, 0x32, 0x9e, 0xa0, 0x2d, 0xf4, 0x2d, 0xd4,
|
||||||
0x0f, 0xf7, 0x09, 0x79, 0x6f, 0x19, 0x21, 0xa7, 0xa1, 0x16, 0x52, 0xf0, 0xff, 0x31, 0x7d, 0xfd,
|
0x24, 0x83, 0x31, 0xb9, 0x15, 0x5b, 0x0f, 0xd0, 0xe4, 0xdc, 0xf6, 0x91, 0x85, 0xd0, 0xff, 0x2a,
|
||||||
0x97, 0xe7, 0x50, 0xf5, 0x3a, 0xd4, 0xdf, 0x62, 0xdb, 0x3a, 0x2e, 0x7e, 0xa7, 0x1e, 0x50, 0xae,
|
0x41, 0x7b, 0xf1, 0xc9, 0x38, 0x4a, 0xf8, 0x03, 0x7c, 0xfd, 0xfd, 0x6d, 0xbe, 0xde, 0x79, 0x88,
|
||||||
0x9a, 0x62, 0x96, 0xed, 0xb8, 0xd8, 0x38, 0x36, 0x47, 0x8e, 0x81, 0x8d, 0xc3, 0x56, 0x49, 0x9f,
|
0xaf, 0xd3, 0x50, 0x4b, 0x19, 0xfa, 0xbf, 0x58, 0x0e, 0xfd, 0xe7, 0xa7, 0x30, 0xf9, 0x3a, 0x34,
|
||||||
0x80, 0x36, 0x5f, 0x90, 0x3c, 0xc1, 0xfb, 0x7d, 0x55, 0x1e, 0x5e, 0xd2, 0xf7, 0x50, 0x49, 0x44,
|
0xde, 0x63, 0xdb, 0x3a, 0x2c, 0x7e, 0xc6, 0xee, 0x30, 0x72, 0x29, 0xc5, 0x2c, 0xdb, 0x21, 0xd8,
|
||||||
0xed, 0x4c, 0x53, 0xc5, 0xb4, 0x3a, 0x4f, 0x35, 0x09, 0xe7, 0x0e, 0xe7, 0xab, 0xc2, 0xf2, 0xdb,
|
0x38, 0x34, 0x47, 0x8e, 0x81, 0x8d, 0x7d, 0xb5, 0xac, 0x4f, 0x41, 0x5b, 0x2c, 0x48, 0x5e, 0xe8,
|
||||||
0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x30, 0x4e, 0x6f, 0x73, 0x17, 0x0a, 0x00, 0x00,
|
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;
|
UNKNOWN_PUSH_NOTIFICATION_TYPE = 0;
|
||||||
MESSAGE = 1;
|
MESSAGE = 1;
|
||||||
MENTION = 2;
|
MENTION = 2;
|
||||||
|
REQUEST_TO_JOIN_COMMUNITY = 3;
|
||||||
}
|
}
|
||||||
bytes author = 7;
|
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/crypto"
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
"github.com/status-im/status-go/eth-node/types"
|
||||||
"github.com/status-im/status-go/protocol/common"
|
"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/protobuf"
|
||||||
"github.com/status-im/status-go/protocol/pushnotificationclient"
|
"github.com/status-im/status-go/protocol/pushnotificationclient"
|
||||||
"github.com/status-im/status-go/protocol/pushnotificationserver"
|
"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/protocol/tt"
|
||||||
"github.com/status-im/status-go/waku"
|
"github.com/status-im/status-go/waku"
|
||||||
)
|
)
|
||||||
@ -843,3 +845,136 @@ func (s *MessengerPushNotificationSuite) TestReceivePushNotificationMention() {
|
|||||||
s.Require().NoError(alice.Shutdown())
|
s.Require().NoError(alice.Shutdown())
|
||||||
s.Require().NoError(server.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
|
// get the public key associated with this query
|
||||||
clientPublicKey, err := c.persistence.GetQueryPublicKey(response.MessageId)
|
clientPublicKey, err := c.persistence.GetQueryPublicKey(response.MessageId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
c.config.Logger.Error("failed to query client publicKey", zap.Error(err))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if clientPublicKey == nil {
|
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))
|
c.config.Logger.Debug("should no mention", zap.Any("publickey", shouldNotify))
|
||||||
// we send the notifications and return the info of the devices notified
|
// 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 {
|
if err != nil {
|
||||||
return err
|
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
|
// 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -1280,9 +1281,9 @@ func (c *Client) registerWithServer(registration *protobuf.PushNotificationRegis
|
|||||||
return nil
|
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
|
// 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
|
// get latest push notification infos
|
||||||
err := c.queryNotificationInfo(publicKey, false)
|
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
|
// one info per installation id, grouped by server
|
||||||
actionableInfos := make(map[string][]*PushNotificationInfo)
|
actionableInfos := make(map[string][]*PushNotificationInfo)
|
||||||
|
|
||||||
|
c.config.Logger.Info("INFOS", zap.Any("info", info))
|
||||||
for _, i := range info {
|
for _, i := range info {
|
||||||
|
|
||||||
if !installationIDsMap[i.InstallationID] {
|
if !installationIDsMap[i.InstallationID] {
|
||||||
@ -1406,7 +1409,7 @@ func (c *Client) resendNotification(pn *SentNotification) error {
|
|||||||
return err
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
|
|
||||||
const defaultNewMessageNotificationText = "You have a new message"
|
const defaultNewMessageNotificationText = "You have a new message"
|
||||||
const defaultMentionNotificationText = "Someone mentioned you"
|
const defaultMentionNotificationText = "Someone mentioned you"
|
||||||
|
const defaultRequestToJoinCommunityNotificationText = "Someone requested to join a community you are an admin of"
|
||||||
|
|
||||||
type GoRushRequestData struct {
|
type GoRushRequestData struct {
|
||||||
EncryptedMessage string `json:"encryptedMessage"`
|
EncryptedMessage string `json:"encryptedMessage"`
|
||||||
@ -56,6 +57,8 @@ func PushNotificationRegistrationToGoRushRequest(requestAndRegistrations []*Requ
|
|||||||
var text string
|
var text string
|
||||||
if request.Type == protobuf.PushNotification_MESSAGE {
|
if request.Type == protobuf.PushNotification_MESSAGE {
|
||||||
text = defaultNewMessageNotificationText
|
text = defaultNewMessageNotificationText
|
||||||
|
} else if request.Type == protobuf.PushNotification_REQUEST_TO_JOIN_COMMUNITY {
|
||||||
|
text = defaultRequestToJoinCommunityNotificationText
|
||||||
} else {
|
} else {
|
||||||
text = defaultMentionNotificationText
|
text = defaultMentionNotificationText
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,8 @@ type Server struct {
|
|||||||
persistence Persistence
|
persistence Persistence
|
||||||
config *Config
|
config *Config
|
||||||
messageProcessor *common.MessageProcessor
|
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 {
|
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 {
|
} else if registration.AccessToken != pn.AccessToken {
|
||||||
s.config.Logger.Debug("invalid token")
|
s.config.Logger.Debug("invalid token")
|
||||||
report.Error = protobuf.PushNotificationReport_WRONG_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")
|
s.config.Logger.Debug("filtered notification")
|
||||||
// We report as successful but don't send the notification
|
// We report as successful but don't send the notification
|
||||||
// for privacy reasons, as otherwise we would disclose that
|
// for privacy reasons, as otherwise we would disclose that
|
||||||
@ -450,6 +452,7 @@ func (s *Server) sendPushNotification(requestAndRegistrations []*RequestAndRegis
|
|||||||
if len(requestAndRegistrations) == 0 {
|
if len(requestAndRegistrations) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
s.SentRequests++
|
||||||
goRushRequest := PushNotificationRegistrationToGoRushRequest(requestAndRegistrations)
|
goRushRequest := PushNotificationRegistrationToGoRushRequest(requestAndRegistrations)
|
||||||
return sendGoRushNotification(goRushRequest, s.config.GorushURL, s.config.Logger)
|
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
|
return pn.Type == protobuf.PushNotification_MESSAGE
|
||||||
}
|
}
|
||||||
|
|
||||||
// isValidMentionNotification checks:
|
// isValidMessageNotification checks:
|
||||||
// this is a message
|
// this is a message
|
||||||
// the chat is not muted
|
// the chat is not muted
|
||||||
// the author is not blocked
|
// the author is not blocked
|
||||||
func (s *Server) isValidMessageNotification(pn *protobuf.PushNotification, registration *protobuf.PushNotificationRegistration) bool {
|
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)
|
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) {
|
func publishMessengerResponse(response *protocol.MessengerResponse) {
|
||||||
if !response.IsEmpty() {
|
if !response.IsEmpty() {
|
||||||
PublisherSignalHandler{}.NewMessages(response)
|
notifications := response.Notifications()
|
||||||
localnotifications.SendMessageNotifications(response.Notifications)
|
|
||||||
// Clear notifications as not used for now
|
// 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 (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"math/big"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"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/multiaccounts/accounts"
|
||||||
"github.com/status-im/status-go/protocol"
|
|
||||||
"github.com/status-im/status-go/services/wallet"
|
"github.com/status-im/status-go/services/wallet"
|
||||||
"github.com/status-im/status-go/signal"
|
"github.com/status-im/status-go/signal"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PushCategory string
|
type PushCategory string
|
||||||
|
|
||||||
type transactionState string
|
|
||||||
|
|
||||||
type NotificationType string
|
type NotificationType string
|
||||||
|
|
||||||
const (
|
type NotificationBody interface {
|
||||||
walletDeeplinkPrefix = "status-im://wallet/"
|
json.Marshaler
|
||||||
|
|
||||||
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 Notification struct {
|
type Notification struct {
|
||||||
ID common.Hash `json:"id"`
|
ID common.Hash
|
||||||
Platform float32 `json:"platform,omitempty"`
|
Platform float32
|
||||||
Body interface{}
|
Body NotificationBody
|
||||||
BodyType NotificationType `json:"bodyType"`
|
BodyType NotificationType
|
||||||
Category PushCategory `json:"category,omitempty"`
|
Title string
|
||||||
Deeplink string `json:"deepLink,omitempty"`
|
Message string
|
||||||
Image string `json:"imageUrl,omitempty"`
|
Category PushCategory
|
||||||
IsScheduled bool `json:"isScheduled,omitempty"`
|
Deeplink string
|
||||||
ScheduledTime string `json:"scheduleTime,omitempty"`
|
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
|
// notificationAlias is an interim struct used for json un/marshalling
|
||||||
type notificationAlias struct {
|
type notificationAlias struct {
|
||||||
ID common.Hash `json:"id"`
|
ID common.Hash `json:"id"`
|
||||||
Platform float32 `json:"platform,omitempty"`
|
Platform float32 `json:"platform,omitempty"`
|
||||||
Body json.RawMessage `json:"body"`
|
Body json.RawMessage `json:"body"`
|
||||||
BodyType NotificationType `json:"bodyType"`
|
BodyType NotificationType `json:"bodyType"`
|
||||||
Category PushCategory `json:"category,omitempty"`
|
Title string `json:"title,omitempty"`
|
||||||
Deeplink string `json:"deepLink,omitempty"`
|
Message string `json:"message,omitempty"`
|
||||||
Image string `json:"imageUrl,omitempty"`
|
Category PushCategory `json:"category,omitempty"`
|
||||||
IsScheduled bool `json:"isScheduled,omitempty"`
|
Deeplink string `json:"deepLink,omitempty"`
|
||||||
ScheduledTime string `json:"scheduleTime,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
|
IsConversation bool `json:"isConversation,omitempty"`
|
||||||
type TransactionEvent struct {
|
IsGroupConversation bool `json:"isGroupConversation,omitempty"`
|
||||||
Type string `json:"type"`
|
ConversationID string `json:"conversationId,omitempty"`
|
||||||
BlockNumber *big.Int `json:"block-number"`
|
Timestamp uint64 `json:"timestamp,omitempty"`
|
||||||
Accounts []common.Address `json:"accounts"`
|
Author NotificationAuthor `json:"notificationAuthor,omitempty"`
|
||||||
NewTransactionsPerAccount map[common.Address]int `json:"new-transactions"`
|
|
||||||
ERC20 bool `json:"erc20"`
|
|
||||||
MaxKnownBlocks map[common.Address]*big.Int `json:"max-known-blocks"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MessageEvent - structure used to pass messages from chat to bus
|
// 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) {
|
func (n *Notification) MarshalJSON() ([]byte, error) {
|
||||||
|
|
||||||
var body json.RawMessage
|
var body json.RawMessage
|
||||||
var err error
|
if n.Body != nil {
|
||||||
|
encodedBody, err := n.Body.MarshalJSON()
|
||||||
switch n.BodyType {
|
if err != nil {
|
||||||
case TypeTransaction:
|
return nil, err
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
body = encodedBody
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
alias := notificationAlias{
|
alias := notificationAlias{
|
||||||
n.ID,
|
ID: n.ID,
|
||||||
n.Platform,
|
Platform: n.Platform,
|
||||||
body,
|
Body: body,
|
||||||
n.BodyType,
|
BodyType: n.BodyType,
|
||||||
n.Category,
|
Category: n.Category,
|
||||||
n.Deeplink,
|
Title: n.Title,
|
||||||
n.Image,
|
Message: n.Message,
|
||||||
n.IsScheduled,
|
Deeplink: n.Deeplink,
|
||||||
n.ScheduledTime,
|
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)
|
return json.Marshal(alias)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Notification) UnmarshalJSON(data []byte) error {
|
func PushMessages(ns []*Notification) {
|
||||||
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) {
|
|
||||||
for _, n := range ns {
|
for _, n := range ns {
|
||||||
pushMessage(n)
|
pushMessage(n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func pushMessage(notification *Notification) {
|
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)
|
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
|
// Start Worker which processes all incoming messages
|
||||||
func (s *Service) Start(_ *p2p.Server) error {
|
func (s *Service) Start(_ *p2p.Server) error {
|
||||||
s.started = true
|
s.started = true
|
||||||
@ -473,19 +225,3 @@ func (s *Service) Protocols() []p2p.Protocol {
|
|||||||
func (s *Service) IsStarted() bool {
|
func (s *Service) IsStarted() bool {
|
||||||
return s.started
|
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 (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -128,19 +128,8 @@ func TestTransactionNotification(t *testing.T) {
|
|||||||
if signalEvent == nil {
|
if signalEvent == nil {
|
||||||
return fmt.Errorf("signal was not handled")
|
return fmt.Errorf("signal was not handled")
|
||||||
}
|
}
|
||||||
notification := struct {
|
require.True(t, strings.Contains(string(signalEvent), `"type":"local-notifications"`))
|
||||||
Type string
|
require.True(t, strings.Contains(string(signalEvent), `"to":"`+header.Address.Hex()))
|
||||||
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")
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}, 2*time.Second, 100*time.Millisecond))
|
}, 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 (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
|
||||||
statusproto "github.com/status-im/status-go/protocol"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
"github.com/status-im/status-go/eth-node/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -158,6 +157,6 @@ func SendWhisperFilterAdded(filters []*Filter) {
|
|||||||
send(EventWhisperFilterAdded, WhisperFilterAddedSignal{Filters: filters})
|
send(EventWhisperFilterAdded, WhisperFilterAddedSignal{Filters: filters})
|
||||||
}
|
}
|
||||||
|
|
||||||
func SendNewMessages(response *statusproto.MessengerResponse) {
|
func SendNewMessages(obj json.Marshaler) {
|
||||||
send(EventNewMessages, response)
|
send(EventNewMessages, obj)
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,8 @@ const (
|
|||||||
// https://notes.status.im/i8Y_l7ccTiOYq09HVgoFwA
|
// https://notes.status.im/i8Y_l7ccTiOYq09HVgoFwA
|
||||||
kdfIterationsNumber = 3200
|
kdfIterationsNumber = 3200
|
||||||
// WALMode for sqlite.
|
// WALMode for sqlite.
|
||||||
WALMode = "wal"
|
WALMode = "wal"
|
||||||
|
inMemoryPath = ":memory:"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DecryptDB completely removes the encryption from the db
|
// DecryptDB completely removes the encryption from the db
|
||||||
@ -97,7 +98,7 @@ func openDB(path, key string) (*sql.DB, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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)
|
return nil, fmt.Errorf("unable to set journal_mode to WAL. actual mode %s", mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user