diff --git a/VERSION b/VERSION index 33707196d..7375dee5f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.71.7 +0.72.0 diff --git a/appdatabase/migrations/bindata.go b/appdatabase/migrations/bindata.go index 4cbb2678c..5016ff6e7 100644 --- a/appdatabase/migrations/bindata.go +++ b/appdatabase/migrations/bindata.go @@ -736,7 +736,7 @@ func _0018_profile_pictures_visibilityUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "0018_profile_pictures_visibility.up.sql", size: 84, mode: os.FileMode(0644), modTime: time.Unix(1611588835, 0)} + info := bindataFileInfo{name: "0018_profile_pictures_visibility.up.sql", size: 84, mode: os.FileMode(0644), modTime: time.Unix(1612251705, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xc9, 0xe3, 0xc5, 0xec, 0x83, 0x55, 0x45, 0x57, 0x7a, 0xaa, 0xd2, 0xa7, 0x59, 0xa7, 0x87, 0xef, 0x63, 0x19, 0x9c, 0x46, 0x9c, 0xc5, 0x32, 0x89, 0xa4, 0x68, 0x70, 0xd8, 0x83, 0x43, 0xa4, 0x72}} return a, nil } diff --git a/protocol/chat.go b/protocol/chat.go index bcba0c5dc..542ac0e4b 100644 --- a/protocol/chat.go +++ b/protocol/chat.go @@ -261,11 +261,11 @@ func OneToOneFromPublicKey(pk *ecdsa.PublicKey, timesource common.TimeSource) *C chatID := types.EncodeHex(crypto.FromECDSAPub(pk)) newChat := CreateOneToOneChat(chatID[:8], pk, timesource) - return &newChat + return newChat } -func CreateOneToOneChat(name string, publicKey *ecdsa.PublicKey, timesource common.TimeSource) Chat { - return Chat{ +func CreateOneToOneChat(name string, publicKey *ecdsa.PublicKey, timesource common.TimeSource) *Chat { + return &Chat{ ID: oneToOneChatID(publicKey), Name: name, Timestamp: int64(timesource.GetCurrentTime()), @@ -274,13 +274,13 @@ func CreateOneToOneChat(name string, publicKey *ecdsa.PublicKey, timesource comm } } -func CreateCommunityChat(orgID, chatID string, orgChat *protobuf.CommunityChat, timesource common.TimeSource) Chat { +func CreateCommunityChat(orgID, chatID string, orgChat *protobuf.CommunityChat, timesource common.TimeSource) *Chat { color := orgChat.Identity.Color if color == "" { color = chatColors[rand.Intn(len(chatColors))] // nolint: gosec } - return Chat{ + return &Chat{ CommunityID: orgID, Name: orgChat.Identity.DisplayName, Active: true, @@ -291,8 +291,8 @@ func CreateCommunityChat(orgID, chatID string, orgChat *protobuf.CommunityChat, } } -func CreateCommunityChats(org *communities.Community, timesource common.TimeSource) []Chat { - var chats []Chat +func CreateCommunityChats(org *communities.Community, timesource common.TimeSource) []*Chat { + var chats []*Chat orgID := org.IDString() for chatID, chat := range org.Chats() { @@ -301,8 +301,8 @@ func CreateCommunityChats(org *communities.Community, timesource common.TimeSour return chats } -func CreatePublicChat(name string, timesource common.TimeSource) Chat { - return Chat{ +func CreatePublicChat(name string, timesource common.TimeSource) *Chat { + return &Chat{ ID: name, Name: name, Active: true, @@ -316,8 +316,8 @@ func buildProfileChatID(publicKeyString string) string { return "@" + publicKeyString } -func CreateProfileChat(id string, profile string, timesource common.TimeSource) Chat { - return Chat{ +func CreateProfileChat(id string, profile string, timesource common.TimeSource) *Chat { + return &Chat{ ID: id, Name: id, Active: true, diff --git a/protocol/common/crypto.go b/protocol/common/crypto.go index b90e67a2f..cd1e802c6 100644 --- a/protocol/common/crypto.go +++ b/protocol/common/crypto.go @@ -75,6 +75,10 @@ func PubkeyToHex(key *ecdsa.PublicKey) string { return types.EncodeHex(crypto.FromECDSAPub(key)) } +func PubkeyToHexBytes(key *ecdsa.PublicKey) types.HexBytes { + return crypto.FromECDSAPub(key) +} + func HexToPubkey(pk string) (*ecdsa.PublicKey, error) { bytes, err := types.DecodeHex(pk) if err != nil { diff --git a/protocol/common/message_processor.go b/protocol/common/message_processor.go index 9f2b7f54b..e944ac5f8 100644 --- a/protocol/common/message_processor.go +++ b/protocol/common/message_processor.go @@ -152,6 +152,23 @@ func (p *MessageProcessor) SendPrivate( return p.sendPrivate(ctx, recipient, rawMessage) } +// SendCommunityMessage takes encoded data, encrypts it and sends through the wire +// using the community topic and their key +func (p *MessageProcessor) SendCommunityMessage( + ctx context.Context, + recipient *ecdsa.PublicKey, + rawMessage RawMessage, +) ([]byte, error) { + p.logger.Debug( + "sending a community message", + zap.String("public-key", types.EncodeHex(crypto.FromECDSAPub(recipient))), + zap.String("site", "SendPrivate"), + ) + rawMessage.Sender = p.identity + + return p.sendCommunity(ctx, recipient, &rawMessage) +} + // SendGroup takes encoded data, encrypts it and sends through the wire, // always return the messageID func (p *MessageProcessor) SendGroup( @@ -186,6 +203,38 @@ func (p *MessageProcessor) SendGroup( return messageID, nil } +// sendCommunity sends data to the recipient identifying with a given public key. +func (p *MessageProcessor) sendCommunity( + ctx context.Context, + recipient *ecdsa.PublicKey, + rawMessage *RawMessage, +) ([]byte, error) { + p.logger.Debug("sending community message", zap.String("recipient", types.EncodeHex(crypto.FromECDSAPub(recipient)))) + + wrappedMessage, err := p.wrapMessageV1(rawMessage) + if err != nil { + return nil, errors.Wrap(err, "failed to wrap message") + } + + messageID := v1protocol.MessageID(&rawMessage.Sender.PublicKey, wrappedMessage) + rawMessage.ID = types.EncodeHex(messageID) + + // Notify before dispatching, otherwise the dispatch subscription might happen + // earlier than the scheduled + p.notifyOnScheduledMessage(rawMessage) + + messageIDs := [][]byte{messageID} + hash, newMessage, err := p.sendCommunityRawMessage(ctx, recipient, wrappedMessage, messageIDs) + if err != nil { + p.logger.Error("failed to send a community message", zap.Error(err)) + return nil, errors.Wrap(err, "failed to send a message spec") + } + + p.transport.Track(messageIDs, hash, newMessage) + + return messageID, nil +} + // sendPrivate sends data to the recipient identifying with a given public key. func (p *MessageProcessor) sendPrivate( ctx context.Context, @@ -619,6 +668,24 @@ func (p *MessageProcessor) sendPrivateRawMessage(ctx context.Context, rawMessage return hash, newMessage, nil } +// sendCommunityRawMessage sends a message not wrapped in an encryption layer +// to a community +func (p *MessageProcessor) sendCommunityRawMessage(ctx context.Context, publicKey *ecdsa.PublicKey, payload []byte, messageIDs [][]byte) ([]byte, *types.NewMessage, error) { + newMessage := &types.NewMessage{ + TTL: whisperTTL, + Payload: payload, + PowTarget: calculatePoW(payload), + PowTime: whisperPoWTime, + } + + hash, err := p.transport.SendCommunityMessage(ctx, newMessage, publicKey) + if err != nil { + return nil, nil, err + } + + return hash, newMessage, nil +} + // sendMessageSpec analyses the spec properties and selects a proper transport method. func (p *MessageProcessor) sendMessageSpec(ctx context.Context, publicKey *ecdsa.PublicKey, messageSpec *encryption.ProtocolMessageSpec, messageIDs [][]byte) ([]byte, *types.NewMessage, error) { newMessage, err := MessageSpecToWhisper(messageSpec) @@ -690,8 +757,8 @@ func (p *MessageProcessor) notifyOnScheduledMessage(message *RawMessage) { } } -func (p *MessageProcessor) JoinPublic(chatID string) error { - return p.transport.JoinPublic(chatID) +func (p *MessageProcessor) JoinPublic(id string) (*transport.Filter, error) { + return p.transport.JoinPublic(id) } // AddEphemeralKey adds an ephemeral key that we will be listening to diff --git a/protocol/communities/community.go b/protocol/communities/community.go index 3f09556cf..fee1a3a01 100644 --- a/protocol/communities/community.go +++ b/protocol/communities/community.go @@ -4,6 +4,7 @@ import ( "bytes" "crypto/ecdsa" "encoding/json" + "errors" "sync" "github.com/golang/protobuf/proto" @@ -11,6 +12,7 @@ import ( "github.com/status-im/status-go/eth-node/crypto" "github.com/status-im/status-go/eth-node/types" + "github.com/status-im/status-go/images" "github.com/status-im/status-go/protocol/common" "github.com/status-im/status-go/protocol/protobuf" "github.com/status-im/status-go/protocol/v1" @@ -24,8 +26,11 @@ type Config struct { MarshaledCommunityDescription []byte ID *ecdsa.PublicKey Joined bool + Requested bool Verified bool Logger *zap.Logger + RequestedToJoinAt uint64 + MemberIdentity *ecdsa.PublicKey } type Community struct { @@ -34,6 +39,10 @@ type Community struct { } func New(config Config) (*Community, error) { + if config.MemberIdentity == nil { + return nil, errors.New("no member identity") + } + if config.Logger == nil { logger, err := zap.NewDevelopment() if err != nil { @@ -47,21 +56,80 @@ func New(config Config) (*Community, error) { return community, nil } +type CommunityChat struct { + ID string `json:"id"` + Name string `json:"name"` + Members map[string]*protobuf.CommunityMember `json:"members"` + Permissions *protobuf.CommunityPermissions `json:"permissions"` + CanPost bool `json:"canPost"` +} + func (o *Community) MarshalJSON() ([]byte, error) { - item := struct { - *protobuf.CommunityDescription `json:"description"` - ID string `json:"id"` - Admin bool `json:"admin"` - Verified bool `json:"verified"` - Joined bool `json:"joined"` - }{ - ID: o.IDString(), - CommunityDescription: o.config.CommunityDescription, - Admin: o.IsAdmin(), - Verified: o.config.Verified, - Joined: o.config.Joined, + if o.config.MemberIdentity == nil { + return nil, errors.New("member identity not set") } - return json.Marshal(item) + communityItem := struct { + ID types.HexBytes `json:"id"` + Admin bool `json:"admin"` + Verified bool `json:"verified"` + Joined bool `json:"joined"` + RequestedAccessAt int `json:"requestedAccessAt"` + Name string `json:"name"` + Description string `json:"description"` + Chats map[string]CommunityChat `json:"chats"` + Images map[string]images.IdentityImage `json:"images"` + Permissions *protobuf.CommunityPermissions `json:"permissions"` + Members map[string]*protobuf.CommunityMember `json:"members"` + CanRequestAccess bool `json:"canRequestAccess"` + CanManageUsers bool `json:"canManageUsers"` + CanJoin bool `json:"canJoin"` + Color string `json:"color"` + RequestedToJoinAt uint64 `json:"requestedToJoinAt,omitempty"` + IsMember bool `json:"isMember"` + }{ + ID: o.ID(), + Admin: o.IsAdmin(), + Verified: o.config.Verified, + Chats: make(map[string]CommunityChat), + Joined: o.config.Joined, + CanRequestAccess: o.CanRequestAccess(o.config.MemberIdentity), + CanJoin: o.canJoin(), + CanManageUsers: o.CanManageUsers(o.config.MemberIdentity), + RequestedToJoinAt: o.RequestedToJoinAt(), + IsMember: o.isMember(), + } + if o.config.CommunityDescription != nil { + for id, c := range o.config.CommunityDescription.Chats { + canPost, err := o.CanPost(o.config.MemberIdentity, id, nil) + if err != nil { + return nil, err + } + chat := CommunityChat{ + ID: id, + Name: c.Identity.DisplayName, + Permissions: c.Permissions, + Members: c.Members, + CanPost: canPost, + } + communityItem.Chats[id] = chat + } + communityItem.Members = o.config.CommunityDescription.Members + communityItem.Permissions = o.config.CommunityDescription.Permissions + if o.config.CommunityDescription.Identity != nil { + communityItem.Name = o.config.CommunityDescription.Identity.DisplayName + communityItem.Color = o.config.CommunityDescription.Identity.Color + communityItem.Description = o.config.CommunityDescription.Identity.Description + for t, i := range o.config.CommunityDescription.Identity.Images { + if communityItem.Images == nil { + communityItem.Images = make(map[string]images.IdentityImage) + } + communityItem.Images[t] = images.IdentityImage{Name: t, Payload: i.Payload} + + } + } + + } + return json.Marshal(communityItem) } func (o *Community) initialize() { @@ -77,23 +145,43 @@ type CommunityChatChanges struct { } type CommunityChanges struct { - MembersAdded map[string]*protobuf.CommunityMember - MembersRemoved map[string]*protobuf.CommunityMember + Community *Community `json:"community"` + MembersAdded map[string]*protobuf.CommunityMember `json:"membersAdded"` + MembersRemoved map[string]*protobuf.CommunityMember `json:"membersRemoved"` - ChatsRemoved map[string]*protobuf.CommunityChat - ChatsAdded map[string]*protobuf.CommunityChat - ChatsModified map[string]*CommunityChatChanges + ChatsRemoved map[string]*protobuf.CommunityChat `json:"chatsRemoved"` + ChatsAdded map[string]*protobuf.CommunityChat `json:"chatsAdded"` + ChatsModified map[string]*CommunityChatChanges `json:"chatsModified"` + + // ShouldMemberJoin indicates whether the user should join this community + // automatically + ShouldMemberJoin bool `json:"memberAdded"` + + // ShouldMemberJoin indicates whether the user should leave this community + // automatically + ShouldMemberLeave bool `json:"memberRemoved"` } -func emptyCommunityChanges() *CommunityChanges { - return &CommunityChanges{ - MembersAdded: make(map[string]*protobuf.CommunityMember), - MembersRemoved: make(map[string]*protobuf.CommunityMember), - - ChatsRemoved: make(map[string]*protobuf.CommunityChat), - ChatsAdded: make(map[string]*protobuf.CommunityChat), - ChatsModified: make(map[string]*CommunityChatChanges), +func (c *CommunityChanges) HasNewMember(identity string) bool { + if len(c.MembersAdded) == 0 { + return false } + _, ok := c.MembersAdded[identity] + return ok +} + +func (c *CommunityChanges) HasMemberLeft(identity string) bool { + if len(c.MembersRemoved) == 0 { + return false + } + _, ok := c.MembersRemoved[identity] + return ok +} + +func (o *Community) emptyCommunityChanges() *CommunityChanges { + changes := emptyCommunityChanges() + changes.Community = o + return changes } func (o *Community) CreateChat(chatID string, chat *protobuf.CommunityChat) (*CommunityChanges, error) { @@ -120,7 +208,7 @@ func (o *Community) CreateChat(chatID string, chat *protobuf.CommunityChat) (*Co o.increaseClock() - changes := emptyCommunityChanges() + changes := o.emptyCommunityChanges() changes.ChatsAdded[chatID] = chat return changes, nil } @@ -221,11 +309,32 @@ func (o *Community) InviteUserToChat(pk *ecdsa.PublicKey, chatID string) (*proto return response, nil } -func (o *Community) hasMember(pk *ecdsa.PublicKey) bool { +func (o *Community) getMember(pk *ecdsa.PublicKey) *protobuf.CommunityMember { key := common.PubkeyToHex(pk) - _, ok := o.config.CommunityDescription.Members[key] - return ok + member := o.config.CommunityDescription.Members[key] + return member +} + +func (o *Community) hasMember(pk *ecdsa.PublicKey) bool { + + member := o.getMember(pk) + return member != nil +} + +func (o *Community) hasPermission(pk *ecdsa.PublicKey, role protobuf.CommunityMember_Roles) bool { + member := o.getMember(pk) + if member == nil { + return false + } + + for _, r := range member.Roles { + if r == role { + return true + } + } + + return false } func (o *Community) HasMember(pk *ecdsa.PublicKey) bool { @@ -294,20 +403,11 @@ func (o *Community) RemoveUserFromOrg(pk *ecdsa.PublicKey) (*protobuf.CommunityD delete(chat.Members, key) } + o.increaseClock() + return o.config.CommunityDescription, nil } -// TODO: this should accept a request from a user to join and perform any validation -func (o *Community) AcceptRequestToJoin(pk *ecdsa.PublicKey) (*protobuf.CommunityRequestJoinResponse, error) { - - return nil, nil -} - -// TODO: this should decline a request from a user to join -func (o *Community) DeclineRequestToJoin(pk *ecdsa.PublicKey) (*protobuf.CommunityRequestJoinResponse, error) { - return nil, nil -} - func (o *Community) Join() { o.config.Joined = true } @@ -320,7 +420,8 @@ func (o *Community) Joined() bool { return o.config.Joined } -func (o *Community) HandleCommunityDescription(signer *ecdsa.PublicKey, description *protobuf.CommunityDescription, rawMessage []byte) (*CommunityChanges, error) { +// UpdateCommunityDescription will update the community to the new community description and return a list of changes +func (o *Community) UpdateCommunityDescription(signer *ecdsa.PublicKey, description *protobuf.CommunityDescription, rawMessage []byte) (*CommunityChanges, error) { o.mutex.Lock() defer o.mutex.Unlock() @@ -333,14 +434,14 @@ func (o *Community) HandleCommunityDescription(signer *ecdsa.PublicKey, descript return nil, err } - response := emptyCommunityChanges() + response := o.emptyCommunityChanges() if description.Clock <= o.config.CommunityDescription.Clock { return response, nil } - // We only calculate changes if we joined the org, otherwise not interested - if o.config.Joined { + // We only calculate changes if we joined the community or we requested access, otherwise not interested + if o.config.Joined || o.config.RequestedToJoinAt > 0 { // Check for new members at the org level for pk, member := range description.Members { if _, ok := o.config.CommunityDescription.Members[pk]; !ok { @@ -423,8 +524,8 @@ func (o *Community) HandleCommunityDescription(signer *ecdsa.PublicKey, descript return response, nil } -// HandleRequestJoin handles a request, checks that the right permissions are applied and returns an CommunityRequestJoinResponse -func (o *Community) HandleRequestJoin(signer *ecdsa.PublicKey, request *protobuf.CommunityRequestJoin) error { +// ValidateRequestToJoin validates a request, checks that the right permissions are applied +func (o *Community) ValidateRequestToJoin(signer *ecdsa.PublicKey, request *protobuf.CommunityRequestToJoin) error { o.mutex.Lock() defer o.mutex.Unlock() @@ -439,15 +540,14 @@ func (o *Community) HandleRequestJoin(signer *ecdsa.PublicKey, request *protobuf } if len(request.ChatId) != 0 { - return o.handleRequestJoinWithChatID(request) + return o.validateRequestToJoinWithChatID(request) } - err := o.handleRequestJoinWithoutChatID(request) + err := o.validateRequestToJoinWithoutChatID(request) if err != nil { return err } - // Store request to join return nil } @@ -455,7 +555,7 @@ func (o *Community) IsAdmin() bool { return o.config.PrivateKey != nil } -func (o *Community) handleRequestJoinWithChatID(request *protobuf.CommunityRequestJoin) error { +func (o *Community) validateRequestToJoinWithChatID(request *protobuf.CommunityRequestToJoin) error { chat, ok := o.config.CommunityDescription.Chats[request.ChatId] @@ -475,7 +575,15 @@ func (o *Community) handleRequestJoinWithChatID(request *protobuf.CommunityReque return nil } -func (o *Community) handleRequestJoinWithoutChatID(request *protobuf.CommunityRequestJoin) error { +func (o *Community) OnRequest() bool { + return o.config.CommunityDescription.Permissions.Access == protobuf.CommunityPermissions_ON_REQUEST +} + +func (o *Community) InvitationOnly() bool { + return o.config.CommunityDescription.Permissions.Access == protobuf.CommunityPermissions_INVITATION_ONLY +} + +func (o *Community) validateRequestToJoinWithoutChatID(request *protobuf.CommunityRequestToJoin) error { // If they want access to the org only, check that the org is ON_REQUEST if o.config.CommunityDescription.Permissions.Access != protobuf.CommunityPermissions_ON_REQUEST { @@ -485,7 +593,7 @@ func (o *Community) handleRequestJoinWithoutChatID(request *protobuf.CommunityRe return nil } -func (o *Community) ID() []byte { +func (o *Community) ID() types.HexBytes { return crypto.CompressPubkey(o.config.ID) } @@ -497,6 +605,10 @@ func (o *Community) PrivateKey() *ecdsa.PrivateKey { return o.config.PrivateKey } +func (o *Community) PublicKey() *ecdsa.PublicKey { + return o.config.ID +} + func (o *Community) marshaledDescription() ([]byte, error) { return proto.Marshal(o.config.CommunityDescription) } @@ -702,6 +814,77 @@ func (o *Community) increaseClock() { o.config.CommunityDescription.Clock = o.nextClock() } +func (o *Community) Clock() uint64 { + return o.config.CommunityDescription.Clock +} + +func (o *Community) CanRequestAccess(pk *ecdsa.PublicKey) bool { + if o.hasMember(pk) { + return false + } + + if o.config.CommunityDescription == nil { + return false + } + + if o.config.CommunityDescription.Permissions == nil { + return false + } + + return o.config.CommunityDescription.Permissions.Access == protobuf.CommunityPermissions_ON_REQUEST +} + +func (o *Community) CanManageUsers(pk *ecdsa.PublicKey) bool { + o.mutex.Lock() + defer o.mutex.Unlock() + + if o.IsAdmin() { + return true + } + + if !o.hasMember(pk) { + return false + } + + return o.hasPermission(pk, protobuf.CommunityMember_ROLE_ALL) || o.hasPermission(pk, protobuf.CommunityMember_ROLE_MANAGE_USERS) + +} +func (o *Community) isMember() bool { + return o.hasMember(o.config.MemberIdentity) +} + +// CanJoin returns whether a user can join the community, only if it's +func (o *Community) canJoin() bool { + if o.config.Joined { + return false + } + + if o.IsAdmin() { + return true + } + + if o.config.CommunityDescription.Permissions.Access == protobuf.CommunityPermissions_NO_MEMBERSHIP { + return true + } + + return o.isMember() +} + +func (o *Community) RequestedToJoinAt() uint64 { + return o.config.RequestedToJoinAt +} + func (o *Community) nextClock() uint64 { return o.config.CommunityDescription.Clock + 1 } + +func emptyCommunityChanges() *CommunityChanges { + return &CommunityChanges{ + MembersAdded: make(map[string]*protobuf.CommunityMember), + MembersRemoved: make(map[string]*protobuf.CommunityMember), + + ChatsRemoved: make(map[string]*protobuf.CommunityChat), + ChatsAdded: make(map[string]*protobuf.CommunityChat), + ChatsModified: make(map[string]*CommunityChatChanges), + } +} diff --git a/protocol/communities/community_test.go b/protocol/communities/community_test.go index 1a143cf4e..f53abad76 100644 --- a/protocol/communities/community_test.go +++ b/protocol/communities/community_test.go @@ -271,7 +271,7 @@ func (s *CommunitySuite) TestDeclineRequestToJoin() { // TEST CASE 3: Valid } -func (s *CommunitySuite) TestHandleRequestJoin() { +func (s *CommunitySuite) TestValidateRequestToJoin() { description := &protobuf.CommunityDescription{} key, err := crypto.GenerateKey() @@ -279,22 +279,22 @@ func (s *CommunitySuite) TestHandleRequestJoin() { signer := &key.PublicKey - request := &protobuf.CommunityRequestJoin{ + request := &protobuf.CommunityRequestToJoin{ EnsName: "donvanvliet.stateofus.eth", CommunityId: s.communityID, } - requestWithChatID := &protobuf.CommunityRequestJoin{ + requestWithChatID := &protobuf.CommunityRequestToJoin{ EnsName: "donvanvliet.stateofus.eth", CommunityId: s.communityID, ChatId: testChatID1, } - requestWithoutENS := &protobuf.CommunityRequestJoin{ + requestWithoutENS := &protobuf.CommunityRequestToJoin{ CommunityId: s.communityID, } - requestWithChatWithoutENS := &protobuf.CommunityRequestJoin{ + requestWithChatWithoutENS := &protobuf.CommunityRequestToJoin{ CommunityId: s.communityID, ChatId: testChatID1, } @@ -313,7 +313,7 @@ func (s *CommunitySuite) TestHandleRequestJoin() { testCases := []struct { name string config Config - request *protobuf.CommunityRequestJoin + request *protobuf.CommunityRequestToJoin signer *ecdsa.PublicKey err error }{ @@ -326,7 +326,7 @@ func (s *CommunitySuite) TestHandleRequestJoin() { }, { name: "not admin", - config: Config{CommunityDescription: description}, + config: Config{MemberIdentity: signer, CommunityDescription: description}, signer: signer, request: request, err: ErrNotAdmin, @@ -421,7 +421,7 @@ func (s *CommunitySuite) TestHandleRequestJoin() { s.Run(tc.name, func() { org, err := New(tc.config) s.Require().NoError(err) - err = org.HandleRequestJoin(tc.signer, tc.request) + err = org.ValidateRequestToJoin(tc.signer, tc.request) s.Require().Equal(tc.err, err) }) } @@ -543,6 +543,10 @@ func (s *CommunitySuite) TestHandleCommunityDescription() { signer := &key.PublicKey + buildChanges := func(c *Community) *CommunityChanges { + return c.emptyCommunityChanges() + } + testCases := []struct { name string description func(*Community) *protobuf.CommunityDescription @@ -554,14 +558,14 @@ func (s *CommunitySuite) TestHandleCommunityDescription() { name: "updated version but no changes", description: s.identicalCommunityDescription, signer: signer, - changes: func(_ *Community) *CommunityChanges { return emptyCommunityChanges() }, + changes: buildChanges, err: nil, }, { name: "updated version but lower clock", description: s.oldCommunityDescription, signer: signer, - changes: func(_ *Community) *CommunityChanges { return emptyCommunityChanges() }, + changes: buildChanges, err: nil, }, { @@ -569,7 +573,7 @@ func (s *CommunitySuite) TestHandleCommunityDescription() { description: s.removedMemberCommunityDescription, signer: signer, changes: func(org *Community) *CommunityChanges { - changes := emptyCommunityChanges() + changes := org.emptyCommunityChanges() changes.MembersRemoved[s.member1Key] = &protobuf.CommunityMember{} changes.ChatsModified[testChatID1] = &CommunityChatChanges{ MembersAdded: make(map[string]*protobuf.CommunityMember), @@ -586,7 +590,7 @@ func (s *CommunitySuite) TestHandleCommunityDescription() { description: s.addedMemberCommunityDescription, signer: signer, changes: func(org *Community) *CommunityChanges { - changes := emptyCommunityChanges() + changes := org.emptyCommunityChanges() changes.MembersAdded[s.member3Key] = &protobuf.CommunityMember{} changes.ChatsModified[testChatID1] = &CommunityChatChanges{ MembersAdded: make(map[string]*protobuf.CommunityMember), @@ -603,7 +607,7 @@ func (s *CommunitySuite) TestHandleCommunityDescription() { description: s.addedChatCommunityDescription, signer: signer, changes: func(org *Community) *CommunityChanges { - changes := emptyCommunityChanges() + changes := org.emptyCommunityChanges() changes.MembersAdded[s.member3Key] = &protobuf.CommunityMember{} changes.ChatsAdded[testChatID2] = &protobuf.CommunityChat{Permissions: &protobuf.CommunityPermissions{Access: protobuf.CommunityPermissions_INVITATION_ONLY}, Members: make(map[string]*protobuf.CommunityMember)} changes.ChatsAdded[testChatID2].Members[s.member3Key] = &protobuf.CommunityMember{} @@ -617,7 +621,7 @@ func (s *CommunitySuite) TestHandleCommunityDescription() { description: s.removedChatCommunityDescription, signer: signer, changes: func(org *Community) *CommunityChanges { - changes := emptyCommunityChanges() + changes := org.emptyCommunityChanges() changes.ChatsRemoved[testChatID1] = org.config.CommunityDescription.Chats[testChatID1] return changes @@ -631,7 +635,7 @@ func (s *CommunitySuite) TestHandleCommunityDescription() { org := s.buildCommunity(signer) org.Join() expectedChanges := tc.changes(org) - actualChanges, err := org.HandleCommunityDescription(tc.signer, tc.description(org), []byte{0x01}) + actualChanges, err := org.UpdateCommunityDescription(tc.signer, tc.description(org), []byte{0x01}) s.Require().Equal(tc.err, err) s.Require().Equal(expectedChanges, actualChanges) }) @@ -712,102 +716,74 @@ func (s *CommunitySuite) emptyCommunityDescriptionWithChat() *protobuf.Community } +func (s *CommunitySuite) newConfig(identity *ecdsa.PrivateKey, description *protobuf.CommunityDescription) Config { + return Config{ + MemberIdentity: &identity.PublicKey, + ID: &identity.PublicKey, + CommunityDescription: description, + PrivateKey: identity, + } +} + func (s *CommunitySuite) configOnRequest() Config { description := s.emptyCommunityDescription() description.Permissions.Access = protobuf.CommunityPermissions_ON_REQUEST - return Config{ - ID: &s.identity.PublicKey, - CommunityDescription: description, - PrivateKey: s.identity, - } + return s.newConfig(s.identity, description) } func (s *CommunitySuite) configInvitationOnly() Config { description := s.emptyCommunityDescription() description.Permissions.Access = protobuf.CommunityPermissions_INVITATION_ONLY - return Config{ - ID: &s.identity.PublicKey, - CommunityDescription: description, - PrivateKey: s.identity, - } + return s.newConfig(s.identity, description) } func (s *CommunitySuite) configNoMembershipOrgNoMembershipChat() Config { description := s.emptyCommunityDescriptionWithChat() description.Permissions.Access = protobuf.CommunityPermissions_NO_MEMBERSHIP description.Chats[testChatID1].Permissions.Access = protobuf.CommunityPermissions_NO_MEMBERSHIP - return Config{ - ID: &s.identity.PublicKey, - CommunityDescription: description, - PrivateKey: s.identity, - } - + return s.newConfig(s.identity, description) } func (s *CommunitySuite) configNoMembershipOrgInvitationOnlyChat() Config { description := s.emptyCommunityDescriptionWithChat() description.Permissions.Access = protobuf.CommunityPermissions_NO_MEMBERSHIP description.Chats[testChatID1].Permissions.Access = protobuf.CommunityPermissions_INVITATION_ONLY - return Config{ - ID: &s.identity.PublicKey, - CommunityDescription: description, - PrivateKey: s.identity, - } + return s.newConfig(s.identity, description) } func (s *CommunitySuite) configInvitationOnlyOrgInvitationOnlyChat() Config { description := s.emptyCommunityDescriptionWithChat() description.Permissions.Access = protobuf.CommunityPermissions_INVITATION_ONLY description.Chats[testChatID1].Permissions.Access = protobuf.CommunityPermissions_INVITATION_ONLY - return Config{ - ID: &s.identity.PublicKey, - CommunityDescription: description, - PrivateKey: s.identity, - } + return s.newConfig(s.identity, description) } func (s *CommunitySuite) configNoMembershipOrgOnRequestChat() Config { description := s.emptyCommunityDescriptionWithChat() description.Permissions.Access = protobuf.CommunityPermissions_NO_MEMBERSHIP description.Chats[testChatID1].Permissions.Access = protobuf.CommunityPermissions_ON_REQUEST - return Config{ - ID: &s.identity.PublicKey, - CommunityDescription: description, - PrivateKey: s.identity, - } + return s.newConfig(s.identity, description) } func (s *CommunitySuite) configOnRequestOrgOnRequestChat() Config { description := s.emptyCommunityDescriptionWithChat() description.Permissions.Access = protobuf.CommunityPermissions_ON_REQUEST description.Chats[testChatID1].Permissions.Access = protobuf.CommunityPermissions_ON_REQUEST - return Config{ - ID: &s.identity.PublicKey, - CommunityDescription: description, - PrivateKey: s.identity, - } + return s.newConfig(s.identity, description) } func (s *CommunitySuite) configOnRequestOrgInvitationOnlyChat() Config { description := s.emptyCommunityDescriptionWithChat() description.Permissions.Access = protobuf.CommunityPermissions_ON_REQUEST description.Chats[testChatID1].Permissions.Access = protobuf.CommunityPermissions_INVITATION_ONLY - return Config{ - ID: &s.identity.PublicKey, - CommunityDescription: description, - PrivateKey: s.identity, - } + return s.newConfig(s.identity, description) } func (s *CommunitySuite) configOnRequestOrgNoMembershipChat() Config { description := s.emptyCommunityDescriptionWithChat() description.Permissions.Access = protobuf.CommunityPermissions_ON_REQUEST description.Chats[testChatID1].Permissions.Access = protobuf.CommunityPermissions_NO_MEMBERSHIP - return Config{ - ID: &s.identity.PublicKey, - CommunityDescription: description, - PrivateKey: s.identity, - } + return s.newConfig(s.identity, description) } func (s *CommunitySuite) configChatENSOnly() Config { @@ -815,22 +791,14 @@ func (s *CommunitySuite) configChatENSOnly() Config { description.Permissions.Access = protobuf.CommunityPermissions_ON_REQUEST description.Chats[testChatID1].Permissions.Access = protobuf.CommunityPermissions_ON_REQUEST description.Chats[testChatID1].Permissions.EnsOnly = true - return Config{ - ID: &s.identity.PublicKey, - CommunityDescription: description, - PrivateKey: s.identity, - } + return s.newConfig(s.identity, description) } func (s *CommunitySuite) configENSOnly() Config { description := s.emptyCommunityDescription() description.Permissions.Access = protobuf.CommunityPermissions_ON_REQUEST description.Permissions.EnsOnly = true - return Config{ - ID: &s.identity.PublicKey, - CommunityDescription: description, - PrivateKey: s.identity, - } + return s.newConfig(s.identity, description) } func (s *CommunitySuite) config() Config { diff --git a/protocol/communities/errors.go b/protocol/communities/errors.go index c59098bd8..c870ed97c 100644 --- a/protocol/communities/errors.go +++ b/protocol/communities/errors.go @@ -15,4 +15,5 @@ var ErrInvalidCommunityDescriptionMemberInChatButNotInOrg = errors.New("invalid var ErrNotAdmin = errors.New("no admin privileges for this community") var ErrInvalidGrant = errors.New("invalid grant") var ErrNotAuthorized = errors.New("not authorized") +var ErrAlreadyMember = errors.New("already a member") var ErrInvalidMessage = errors.New("invalid community description message") diff --git a/protocol/communities/manager.go b/protocol/communities/manager.go index 8dc195b3e..31f8c3607 100644 --- a/protocol/communities/manager.go +++ b/protocol/communities/manager.go @@ -3,6 +3,7 @@ package communities import ( "crypto/ecdsa" "database/sql" + "time" "github.com/golang/protobuf/proto" @@ -12,16 +13,27 @@ import ( "github.com/status-im/status-go/eth-node/crypto" "github.com/status-im/status-go/eth-node/types" + "github.com/status-im/status-go/protocol/common" + "github.com/status-im/status-go/protocol/ens" "github.com/status-im/status-go/protocol/protobuf" + "github.com/status-im/status-go/protocol/requests" ) type Manager struct { - persistence *Persistence - subscriptions []chan *Subscription - logger *zap.Logger + persistence *Persistence + ensSubscription chan []*ens.VerificationRecord + subscriptions []chan *Subscription + ensVerifier *ens.Verifier + identity *ecdsa.PublicKey + logger *zap.Logger + quit chan struct{} } -func NewManager(db *sql.DB, logger *zap.Logger) (*Manager, error) { +func NewManager(identity *ecdsa.PublicKey, db *sql.DB, logger *zap.Logger, verifier *ens.Verifier) (*Manager, error) { + if identity == nil { + return nil, errors.New("empty identity") + } + var err error if logger == nil { if logger, err = zap.NewDevelopment(); err != nil { @@ -29,18 +41,34 @@ func NewManager(db *sql.DB, logger *zap.Logger) (*Manager, error) { } } - return &Manager{ - logger: logger, + manager := &Manager{ + logger: logger, + identity: identity, + quit: make(chan struct{}), persistence: &Persistence{ logger: logger, db: db, }, - }, nil + } + + if verifier != nil { + + sub := verifier.Subscribe() + manager.ensSubscription = sub + manager.ensVerifier = verifier + } + + return manager, nil } type Subscription struct { - Community *Community - Invitation *protobuf.CommunityInvitation + Community *Community + Invitations []*protobuf.CommunityInvitation +} + +type CommunityResponse struct { + Community *Community `json:"community"` + Changes *CommunityChanges `json:"changes"` } func (m *Manager) Subscribe() chan *Subscription { @@ -49,7 +77,34 @@ func (m *Manager) Subscribe() chan *Subscription { return subscription } +func (m *Manager) Start() error { + if m.ensVerifier != nil { + m.runENSVerificationLoop() + } + return nil +} + +func (m *Manager) runENSVerificationLoop() { + go func() { + for { + select { + case <-m.quit: + m.logger.Debug("quitting ens verification loop") + return + case records, more := <-m.ensSubscription: + if !more { + m.logger.Debug("no more ens records, quitting") + return + } + m.logger.Info("received records", zap.Any("records", records)) + + } + } + }() +} + func (m *Manager) Stop() error { + close(m.quit) for _, c := range m.subscriptions { close(c) } @@ -67,15 +122,15 @@ func (m *Manager) publish(subscription *Subscription) { } func (m *Manager) All() ([]*Community, error) { - return m.persistence.AllCommunities() + return m.persistence.AllCommunities(m.identity) } func (m *Manager) Joined() ([]*Community, error) { - return m.persistence.JoinedCommunities() + return m.persistence.JoinedCommunities(m.identity) } func (m *Manager) Created() ([]*Community, error) { - return m.persistence.CreatedCommunities() + return m.persistence.CreatedCommunities(m.identity) } // CreateCommunity takes a description, generates an ID for it, saves it and return it @@ -97,6 +152,7 @@ func (m *Manager) CreateCommunity(description *protobuf.CommunityDescription) (* PrivateKey: key, Logger: m.logger, Joined: true, + MemberIdentity: m.identity, CommunityDescription: description, } community, err := New(config) @@ -104,6 +160,9 @@ func (m *Manager) CreateCommunity(description *protobuf.CommunityDescription) (* return nil, err } + // We join any community we create + community.Join() + err = m.persistence.SaveCommunity(community) if err != nil { return nil, err @@ -114,8 +173,8 @@ func (m *Manager) CreateCommunity(description *protobuf.CommunityDescription) (* return community, nil } -func (m *Manager) ExportCommunity(idString string) (*ecdsa.PrivateKey, error) { - community, err := m.GetByIDString(idString) +func (m *Manager) ExportCommunity(id types.HexBytes) (*ecdsa.PrivateKey, error) { + community, err := m.GetByID(id) if err != nil { return nil, err } @@ -130,7 +189,7 @@ func (m *Manager) ExportCommunity(idString string) (*ecdsa.PrivateKey, error) { func (m *Manager) ImportCommunity(key *ecdsa.PrivateKey) (*Community, error) { communityID := crypto.CompressPubkey(&key.PublicKey) - community, err := m.persistence.GetByID(communityID) + community, err := m.persistence.GetByID(m.identity, communityID) if err != nil { return nil, err } @@ -145,6 +204,7 @@ func (m *Manager) ImportCommunity(key *ecdsa.PrivateKey) (*Community, error) { PrivateKey: key, Logger: m.logger, Joined: true, + MemberIdentity: m.identity, CommunityDescription: description, } community, err = New(config) @@ -163,8 +223,8 @@ func (m *Manager) ImportCommunity(key *ecdsa.PrivateKey) (*Community, error) { return community, nil } -func (m *Manager) CreateChat(idString string, chat *protobuf.CommunityChat) (*Community, *CommunityChanges, error) { - community, err := m.GetByIDString(idString) +func (m *Manager) CreateChat(communityID types.HexBytes, chat *protobuf.CommunityChat) (*Community, *CommunityChanges, error) { + community, err := m.GetByID(communityID) if err != nil { return nil, nil, err } @@ -188,9 +248,9 @@ func (m *Manager) CreateChat(idString string, chat *protobuf.CommunityChat) (*Co return community, changes, nil } -func (m *Manager) HandleCommunityDescriptionMessage(signer *ecdsa.PublicKey, description *protobuf.CommunityDescription, payload []byte) (*Community, error) { +func (m *Manager) HandleCommunityDescriptionMessage(signer *ecdsa.PublicKey, description *protobuf.CommunityDescription, payload []byte) (*CommunityResponse, error) { id := crypto.CompressPubkey(signer) - community, err := m.persistence.GetByID(id) + community, err := m.persistence.GetByID(m.identity, id) if err != nil { return nil, err } @@ -200,6 +260,7 @@ func (m *Manager) HandleCommunityDescriptionMessage(signer *ecdsa.PublicKey, des CommunityDescription: description, Logger: m.logger, MarshaledCommunityDescription: payload, + MemberIdentity: m.identity, ID: signer, } @@ -209,21 +270,52 @@ func (m *Manager) HandleCommunityDescriptionMessage(signer *ecdsa.PublicKey, des } } - _, err = community.HandleCommunityDescription(signer, description, payload) + changes, err := community.UpdateCommunityDescription(signer, description, payload) if err != nil { return nil, err } + pkString := common.PubkeyToHex(m.identity) + + // If the community require membership, we set whether we should leave/join the community after a state change + if community.InvitationOnly() || community.OnRequest() { + if changes.HasNewMember(pkString) { + hasPendingRequest, err := m.persistence.HasPendingRequestsToJoinForUserAndCommunity(pkString, changes.Community.ID()) + if err != nil { + return nil, err + } + // If there's any pending request, we should join the community + // automatically + changes.ShouldMemberJoin = hasPendingRequest + } + + if changes.HasMemberLeft(pkString) { + // If we joined previously the community, we should leave it + changes.ShouldMemberLeave = community.Joined() + } + } + err = m.persistence.SaveCommunity(community) if err != nil { return nil, err } - return community, nil + // We mark our requests as completed, though maybe we should mark + // any request for any user that has been added as completed + if err := m.markRequestToJoin(m.identity, community); err != nil { + return nil, err + } + // Check if there's a change and we should be joining + + return &CommunityResponse{ + Community: community, + Changes: changes, + }, nil } -// TODO: Finish implementing this -func (m *Manager) HandleCommunityInvitation(signer *ecdsa.PublicKey, invitation *protobuf.CommunityInvitation, payload []byte) (*Community, error) { +// TODO: This is not fully implemented, we want to save the grant passed at +// this stage and make sure it's used when publishing. +func (m *Manager) HandleCommunityInvitation(signer *ecdsa.PublicKey, invitation *protobuf.CommunityInvitation, payload []byte) (*CommunityResponse, error) { m.logger.Debug("Handling wrapped community description message") community, err := m.HandleWrappedCommunityDescriptionMessage(payload) @@ -236,7 +328,80 @@ func (m *Manager) HandleCommunityInvitation(signer *ecdsa.PublicKey, invitation return community, nil } -func (m *Manager) HandleWrappedCommunityDescriptionMessage(payload []byte) (*Community, error) { +// markRequestToJoin marks all the pending requests to join as completed +// if we are members +func (m *Manager) markRequestToJoin(pk *ecdsa.PublicKey, community *Community) error { + if community.HasMember(pk) { + return m.persistence.SetRequestToJoinState(common.PubkeyToHex(pk), community.ID(), RequestToJoinStateAccepted) + } + return nil +} + +func (m *Manager) AcceptRequestToJoin(request *requests.AcceptRequestToJoinCommunity) (*Community, error) { + dbRequest, err := m.persistence.GetRequestToJoin(request.ID) + if err != nil { + return nil, err + } + + community, err := m.GetByID(dbRequest.CommunityID) + if err != nil { + return nil, err + } + + pk, err := common.HexToPubkey(dbRequest.PublicKey) + if err != nil { + return nil, err + } + + return m.inviteUsersToCommunity(community, []*ecdsa.PublicKey{pk}) +} + +func (m *Manager) DeclineRequestToJoin(request *requests.DeclineRequestToJoinCommunity) error { + dbRequest, err := m.persistence.GetRequestToJoin(request.ID) + if err != nil { + return err + } + + return m.persistence.SetRequestToJoinState(dbRequest.PublicKey, dbRequest.CommunityID, RequestToJoinStateDeclined) + +} + +func (m *Manager) HandleCommunityRequestToJoin(signer *ecdsa.PublicKey, request *protobuf.CommunityRequestToJoin) (*RequestToJoin, error) { + community, err := m.persistence.GetByID(m.identity, request.CommunityId) + if err != nil { + return nil, err + } + if community == nil { + return nil, ErrOrgNotFound + } + + // If they are already a member, ignore + if community.HasMember(signer) { + return nil, ErrAlreadyMember + } + + if err := community.ValidateRequestToJoin(signer, request); err != nil { + return nil, err + } + + requestToJoin := &RequestToJoin{ + PublicKey: common.PubkeyToHex(signer), + Clock: request.Clock, + ENSName: request.EnsName, + CommunityID: request.CommunityId, + State: RequestToJoinStatePending, + } + + requestToJoin.CalculateID() + + if err := m.persistence.SaveRequestToJoin(requestToJoin); err != nil { + return nil, err + } + + return requestToJoin, nil +} + +func (m *Manager) HandleWrappedCommunityDescriptionMessage(payload []byte) (*CommunityResponse, error) { m.logger.Debug("Handling wrapped community description message") applicationMetadataMessage := &protobuf.ApplicationMetadataMessage{} @@ -262,8 +427,8 @@ func (m *Manager) HandleWrappedCommunityDescriptionMessage(payload []byte) (*Com return m.HandleCommunityDescriptionMessage(signer, description, payload) } -func (m *Manager) JoinCommunity(idString string) (*Community, error) { - community, err := m.GetByIDString(idString) +func (m *Manager) JoinCommunity(id types.HexBytes) (*Community, error) { + community, err := m.GetByID(id) if err != nil { return nil, err } @@ -278,8 +443,8 @@ func (m *Manager) JoinCommunity(idString string) (*Community, error) { return community, nil } -func (m *Manager) LeaveCommunity(idString string) (*Community, error) { - community, err := m.GetByIDString(idString) +func (m *Manager) LeaveCommunity(id types.HexBytes) (*Community, error) { + community, err := m.GetByID(id) if err != nil { return nil, err } @@ -294,8 +459,33 @@ func (m *Manager) LeaveCommunity(idString string) (*Community, error) { return community, nil } -func (m *Manager) InviteUserToCommunity(idString string, pk *ecdsa.PublicKey) (*Community, error) { - community, err := m.GetByIDString(idString) +func (m *Manager) inviteUsersToCommunity(community *Community, pks []*ecdsa.PublicKey) (*Community, error) { + var invitations []*protobuf.CommunityInvitation + for _, pk := range pks { + invitation, err := community.InviteUserToOrg(pk) + if err != nil { + return nil, err + } + // We mark the user request (if any) as completed + if err := m.markRequestToJoin(pk, community); err != nil { + return nil, err + } + + invitations = append(invitations, invitation) + } + + err := m.persistence.SaveCommunity(community) + if err != nil { + return nil, err + } + + m.publish(&Subscription{Community: community, Invitations: invitations}) + + return community, nil +} + +func (m *Manager) InviteUsersToCommunity(communityID types.HexBytes, pks []*ecdsa.PublicKey) (*Community, error) { + community, err := m.GetByID(communityID) if err != nil { return nil, err } @@ -303,23 +493,11 @@ func (m *Manager) InviteUserToCommunity(idString string, pk *ecdsa.PublicKey) (* return nil, ErrOrgNotFound } - invitation, err := community.InviteUserToOrg(pk) - if err != nil { - return nil, err - } - - err = m.persistence.SaveCommunity(community) - if err != nil { - return nil, err - } - - m.publish(&Subscription{Community: community, Invitation: invitation}) - - return community, nil + return m.inviteUsersToCommunity(community, pks) } -func (m *Manager) RemoveUserFromCommunity(idString string, pk *ecdsa.PublicKey) (*Community, error) { - community, err := m.GetByIDString(idString) +func (m *Manager) RemoveUserFromCommunity(id types.HexBytes, pk *ecdsa.PublicKey) (*Community, error) { + community, err := m.GetByID(id) if err != nil { return nil, err } @@ -342,16 +520,60 @@ func (m *Manager) RemoveUserFromCommunity(idString string, pk *ecdsa.PublicKey) return community, nil } +func (m *Manager) GetByID(id []byte) (*Community, error) { + return m.persistence.GetByID(m.identity, id) +} + func (m *Manager) GetByIDString(idString string) (*Community, error) { id, err := types.DecodeHex(idString) if err != nil { return nil, err } - return m.persistence.GetByID(id) + return m.GetByID(id) } -func (m *Manager) CanPost(pk *ecdsa.PublicKey, orgIDString, chatID string, grant []byte) (bool, error) { - community, err := m.GetByIDString(orgIDString) +func (m *Manager) RequestToJoin(requester *ecdsa.PublicKey, request *requests.RequestToJoinCommunity) (*Community, *RequestToJoin, error) { + community, err := m.persistence.GetByID(m.identity, request.CommunityID) + if err != nil { + return nil, nil, err + } + + // We don't allow requesting access if already a member + if community.HasMember(m.identity) { + return nil, nil, ErrAlreadyMember + } + + clock := uint64(time.Now().Unix()) + requestToJoin := &RequestToJoin{ + PublicKey: common.PubkeyToHex(requester), + Clock: clock, + ENSName: request.ENSName, + CommunityID: request.CommunityID, + State: RequestToJoinStatePending, + Our: true, + } + + requestToJoin.CalculateID() + + if err := m.persistence.SaveRequestToJoin(requestToJoin); err != nil { + return nil, nil, err + } + community.config.RequestedToJoinAt = uint64(time.Now().Unix()) + + return community, requestToJoin, nil +} + +func (m *Manager) PendingRequestsToJoinForUser(pk *ecdsa.PublicKey) ([]*RequestToJoin, error) { + return m.persistence.PendingRequestsToJoinForUser(common.PubkeyToHex(pk)) +} + +func (m *Manager) PendingRequestsToJoinForCommunity(id types.HexBytes) ([]*RequestToJoin, error) { + m.logger.Info("fetching pending invitations", zap.String("community-id", id.String())) + return m.persistence.PendingRequestsToJoinForCommunity(id) +} + +func (m *Manager) CanPost(pk *ecdsa.PublicKey, communityID string, chatID string, grant []byte) (bool, error) { + community, err := m.GetByIDString(communityID) if err != nil { return false, err } diff --git a/protocol/communities/manager_test.go b/protocol/communities/manager_test.go index 2c9ca05a8..8198d3545 100644 --- a/protocol/communities/manager_test.go +++ b/protocol/communities/manager_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/suite" + "github.com/status-im/status-go/eth-node/crypto" "github.com/status-im/status-go/protocol/protobuf" "github.com/status-im/status-go/protocol/sqlite" ) @@ -25,8 +26,12 @@ type ManagerSuite struct { func (s *ManagerSuite) SetupTest() { db, err := sqlite.OpenInMemory() s.Require().NoError(err) - m, err := NewManager(db, nil) + key, err := crypto.GenerateKey() s.Require().NoError(err) + s.Require().NoError(err) + m, err := NewManager(&key.PublicKey, db, nil, nil) + s.Require().NoError(err) + s.Require().NoError(m.Start()) s.manager = m } diff --git a/protocol/communities/persistence.go b/protocol/communities/persistence.go index 6eefd63c5..06227e969 100644 --- a/protocol/communities/persistence.go +++ b/protocol/communities/persistence.go @@ -1,13 +1,16 @@ package communities import ( + "context" "crypto/ecdsa" "database/sql" + "errors" "github.com/golang/protobuf/proto" "go.uber.org/zap" "github.com/status-im/status-go/eth-node/crypto" + "github.com/status-im/status-go/protocol/common" "github.com/status-im/status-go/protocol/protobuf" ) @@ -16,6 +19,8 @@ type Persistence struct { logger *zap.Logger } +const communitiesBaseQuery = `SELECT c.id, c.private_key, c.description,c.joined,c.verified,r.clock FROM communities_communities c LEFT JOIN communities_requests_to_join r ON c.id = r.community_id AND r.public_key = ?` + func (p *Persistence) SaveCommunity(community *Community) error { id := community.ID() privateKey := community.PrivateKey() @@ -28,9 +33,9 @@ func (p *Persistence) SaveCommunity(community *Community) error { return err } -func (p *Persistence) queryCommunities(query string) (response []*Community, err error) { +func (p *Persistence) queryCommunities(memberIdentity *ecdsa.PublicKey, query string) (response []*Community, err error) { - rows, err := p.db.Query(query) + rows, err := p.db.Query(query, common.PubkeyToHex(memberIdentity)) if err != nil { return nil, err } @@ -49,12 +54,13 @@ func (p *Persistence) queryCommunities(query string) (response []*Community, err var publicKeyBytes, privateKeyBytes, descriptionBytes []byte var joined bool var verified bool - err := rows.Scan(&publicKeyBytes, &privateKeyBytes, &descriptionBytes, &joined, &verified) + var requestedToJoinAt sql.NullInt64 + err := rows.Scan(&publicKeyBytes, &privateKeyBytes, &descriptionBytes, &joined, &verified, &requestedToJoinAt) if err != nil { return nil, err } - org, err := unmarshalCommunityFromDB(publicKeyBytes, privateKeyBytes, descriptionBytes, joined, verified, p.logger) + org, err := unmarshalCommunityFromDB(memberIdentity, publicKeyBytes, privateKeyBytes, descriptionBytes, joined, verified, uint64(requestedToJoinAt.Int64), p.logger) if err != nil { return nil, err } @@ -65,27 +71,27 @@ func (p *Persistence) queryCommunities(query string) (response []*Community, err } -func (p *Persistence) AllCommunities() ([]*Community, error) { - query := `SELECT id, private_key, description,joined,verified FROM communities_communities` - return p.queryCommunities(query) +func (p *Persistence) AllCommunities(memberIdentity *ecdsa.PublicKey) ([]*Community, error) { + return p.queryCommunities(memberIdentity, communitiesBaseQuery) } -func (p *Persistence) JoinedCommunities() ([]*Community, error) { - query := `SELECT id, private_key, description,joined,verified FROM communities_communities WHERE joined` - return p.queryCommunities(query) +func (p *Persistence) JoinedCommunities(memberIdentity *ecdsa.PublicKey) ([]*Community, error) { + query := communitiesBaseQuery + ` WHERE c.joined` + return p.queryCommunities(memberIdentity, query) } -func (p *Persistence) CreatedCommunities() ([]*Community, error) { - query := `SELECT id, private_key, description,joined,verified FROM communities_communities WHERE private_key IS NOT NULL` - return p.queryCommunities(query) +func (p *Persistence) CreatedCommunities(memberIdentity *ecdsa.PublicKey) ([]*Community, error) { + query := communitiesBaseQuery + ` WHERE c.private_key IS NOT NULL` + return p.queryCommunities(memberIdentity, query) } -func (p *Persistence) GetByID(id []byte) (*Community, error) { +func (p *Persistence) GetByID(memberIdentity *ecdsa.PublicKey, id []byte) (*Community, error) { var publicKeyBytes, privateKeyBytes, descriptionBytes []byte var joined bool var verified bool + var requestedToJoinAt sql.NullInt64 - err := p.db.QueryRow(`SELECT id, private_key, description, joined,verified FROM communities_communities WHERE id = ?`, id).Scan(&publicKeyBytes, &privateKeyBytes, &descriptionBytes, &joined, &verified) + err := p.db.QueryRow(communitiesBaseQuery+` WHERE c.id = ?`, common.PubkeyToHex(memberIdentity), id).Scan(&publicKeyBytes, &privateKeyBytes, &descriptionBytes, &joined, &verified, &requestedToJoinAt) if err == sql.ErrNoRows { return nil, nil @@ -93,10 +99,10 @@ func (p *Persistence) GetByID(id []byte) (*Community, error) { return nil, err } - return unmarshalCommunityFromDB(publicKeyBytes, privateKeyBytes, descriptionBytes, joined, verified, p.logger) + return unmarshalCommunityFromDB(memberIdentity, publicKeyBytes, privateKeyBytes, descriptionBytes, joined, verified, uint64(requestedToJoinAt.Int64), p.logger) } -func unmarshalCommunityFromDB(publicKeyBytes, privateKeyBytes, descriptionBytes []byte, joined, verified bool, logger *zap.Logger) (*Community, error) { +func unmarshalCommunityFromDB(memberIdentity *ecdsa.PublicKey, publicKeyBytes, privateKeyBytes, descriptionBytes []byte, joined, verified bool, requestedToJoinAt uint64, logger *zap.Logger) (*Community, error) { var privateKey *ecdsa.PrivateKey var err error @@ -129,11 +135,106 @@ func unmarshalCommunityFromDB(publicKeyBytes, privateKeyBytes, descriptionBytes config := Config{ PrivateKey: privateKey, CommunityDescription: description, + MemberIdentity: memberIdentity, MarshaledCommunityDescription: descriptionBytes, Logger: logger, ID: id, Verified: verified, + RequestedToJoinAt: requestedToJoinAt, Joined: joined, } return New(config) } + +func (p *Persistence) SaveRequestToJoin(request *RequestToJoin) (err error) { + tx, err := p.db.BeginTx(context.Background(), &sql.TxOptions{}) + if err != nil { + return err + } + + defer func() { + if err == nil { + err = tx.Commit() + return + } + // don't shadow original error + _ = tx.Rollback() + }() + + var clock uint64 + // Fetch any existing request to join + err = tx.QueryRow(`SELECT clock FROM communities_requests_to_join WHERE state = ? AND public_key = ? AND community_id = ?`, RequestToJoinStatePending, request.PublicKey, request.CommunityID).Scan(&clock) + if err != nil && err != sql.ErrNoRows { + return err + } + + // This is already processed + if clock >= request.Clock { + return errors.New("old request to join") + } + + _, err = tx.Exec(`INSERT INTO communities_requests_to_join(id,public_key,clock,ens_name,chat_id,community_id,state) VALUES (?, ?, ?, ?, ?, ?, ?)`, request.ID, request.PublicKey, request.Clock, request.ENSName, request.ChatID, request.CommunityID, request.State) + return err +} + +func (p *Persistence) PendingRequestsToJoinForUser(pk string) ([]*RequestToJoin, error) { + var requests []*RequestToJoin + rows, err := p.db.Query(`SELECT id,public_key,clock,ens_name,chat_id,community_id,state FROM communities_requests_to_join WHERE state = ? AND public_key = ?`, RequestToJoinStatePending, pk) + if err != nil { + return nil, err + } + defer rows.Close() + + for rows.Next() { + request := &RequestToJoin{} + err := rows.Scan(&request.ID, &request.PublicKey, &request.Clock, &request.ENSName, &request.ChatID, &request.CommunityID, &request.State) + if err != nil { + return nil, err + } + requests = append(requests, request) + } + return requests, nil +} + +func (p *Persistence) HasPendingRequestsToJoinForUserAndCommunity(userPk string, communityID []byte) (bool, error) { + var count int + err := p.db.QueryRow(`SELECT count(1) FROM communities_requests_to_join WHERE state = ? AND public_key = ? AND community_id = ?`, RequestToJoinStatePending, userPk, communityID).Scan(&count) + if err != nil { + return false, err + } + return count > 0, nil +} + +func (p *Persistence) PendingRequestsToJoinForCommunity(id []byte) ([]*RequestToJoin, error) { + var requests []*RequestToJoin + rows, err := p.db.Query(`SELECT id,public_key,clock,ens_name,chat_id,community_id,state FROM communities_requests_to_join WHERE state = ? AND community_id = ?`, RequestToJoinStatePending, id) + if err != nil { + return nil, err + } + defer rows.Close() + + for rows.Next() { + request := &RequestToJoin{} + err := rows.Scan(&request.ID, &request.PublicKey, &request.Clock, &request.ENSName, &request.ChatID, &request.CommunityID, &request.State) + if err != nil { + return nil, err + } + requests = append(requests, request) + } + return requests, nil +} + +func (p *Persistence) SetRequestToJoinState(pk string, communityID []byte, state uint) error { + _, err := p.db.Exec(`UPDATE communities_requests_to_join SET state = ? WHERE community_id = ? AND public_key = ?`, state, communityID, pk) + return err +} + +func (p *Persistence) GetRequestToJoin(id []byte) (*RequestToJoin, error) { + request := &RequestToJoin{} + err := p.db.QueryRow(`SELECT id,public_key,clock,ens_name,chat_id,community_id,state FROM communities_requests_to_join WHERE id = ?`, id).Scan(&request.ID, &request.PublicKey, &request.Clock, &request.ENSName, &request.ChatID, &request.CommunityID, &request.State) + if err != nil { + return nil, err + } + + return request, nil +} diff --git a/protocol/communities/request_to_join.go b/protocol/communities/request_to_join.go new file mode 100644 index 000000000..a6d5eb74a --- /dev/null +++ b/protocol/communities/request_to_join.go @@ -0,0 +1,30 @@ +package communities + +import ( + "fmt" + + "github.com/status-im/status-go/eth-node/crypto" + "github.com/status-im/status-go/eth-node/types" +) + +const ( + RequestToJoinStatePending uint = iota + 1 + RequestToJoinStateDeclined + RequestToJoinStateAccepted +) + +type RequestToJoin struct { + ID types.HexBytes `json:"id"` + PublicKey string `json:"publicKey"` + Clock uint64 `json:"clock"` + ENSName string `json:"ensName,omitempty"` + ChatID string `json:"chatId"` + CommunityID types.HexBytes `json:"communityId"` + State uint `json:"state"` + Our bool `json:"our"` +} + +func (r *RequestToJoin) CalculateID() { + idString := fmt.Sprintf("%s-%s", r.PublicKey, r.CommunityID) + r.ID = crypto.Keccak256([]byte(idString)) +} diff --git a/protocol/communities_messenger_test.go b/protocol/communities_messenger_test.go index 8a6887cc2..3075b2ca6 100644 --- a/protocol/communities_messenger_test.go +++ b/protocol/communities_messenger_test.go @@ -1,6 +1,7 @@ package protocol import ( + "bytes" "context" "crypto/ecdsa" "errors" @@ -16,7 +17,9 @@ import ( "github.com/status-im/status-go/eth-node/crypto" "github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/protocol/common" + "github.com/status-im/status-go/protocol/communities" "github.com/status-im/status-go/protocol/protobuf" + "github.com/status-im/status-go/protocol/requests" "github.com/status-im/status-go/protocol/tt" "github.com/status-im/status-go/waku" ) @@ -95,21 +98,18 @@ func (s *MessengerCommunitiesSuite) newMessenger() *Messenger { func (s *MessengerCommunitiesSuite) TestRetrieveCommunity() { alice := s.newMessenger() - description := &protobuf.CommunityDescription{ - Permissions: &protobuf.CommunityPermissions{ - Access: protobuf.CommunityPermissions_NO_MEMBERSHIP, - }, - Identity: &protobuf.ChatIdentity{ - DisplayName: "status", - Description: "status community description", - }, + description := &requests.CreateCommunity{ + Membership: protobuf.CommunityPermissions_NO_MEMBERSHIP, + Name: "status", + Color: "#ffffff", + Description: "status community description", } response, err := s.bob.CreateCommunity(description) s.Require().NoError(err) s.Require().NotNil(response) - s.Require().Len(response.Communities, 1) - community := response.Communities[0] + s.Require().Len(response.Communities(), 1) + community := response.Communities()[0] // Send an community message chat := CreateOneToOneChat(common.PubkeyToHex(&alice.identity.PublicKey), &alice.identity.PublicKey, s.alice.transport) @@ -119,7 +119,7 @@ func (s *MessengerCommunitiesSuite) TestRetrieveCommunity() { inputMessage.Text = "some text" inputMessage.CommunityID = community.IDString() - err = s.bob.SaveChat(&chat) + err = s.bob.SaveChat(chat) s.NoError(err) _, err = s.bob.SendChatMessage(context.Background(), inputMessage) s.NoError(err) @@ -130,7 +130,7 @@ func (s *MessengerCommunitiesSuite) TestRetrieveCommunity() { if err != nil { return err } - if len(response.Communities) == 0 { + if len(response.Communities()) == 0 { return errors.New("community not received") } return nil @@ -140,29 +140,27 @@ func (s *MessengerCommunitiesSuite) TestRetrieveCommunity() { communities, err := alice.Communities() s.Require().NoError(err) s.Require().Len(communities, 2) - s.Require().Len(response.Communities, 1) + s.Require().Len(response.Communities(), 1) s.Require().Len(response.Messages, 1) s.Require().Equal(community.IDString(), response.Messages[0].CommunityID) } func (s *MessengerCommunitiesSuite) TestJoinCommunity() { - description := &protobuf.CommunityDescription{ - Permissions: &protobuf.CommunityPermissions{ - Access: protobuf.CommunityPermissions_NO_MEMBERSHIP, - }, - Identity: &protobuf.ChatIdentity{ - DisplayName: "status", - Description: "status community description", - }, + + description := &requests.CreateCommunity{ + Membership: protobuf.CommunityPermissions_NO_MEMBERSHIP, + Name: "status", + Color: "#ffffff", + Description: "status community description", } // Create an community chat response, err := s.bob.CreateCommunity(description) s.Require().NoError(err) s.Require().NotNil(response) - s.Require().Len(response.Communities, 1) + s.Require().Len(response.Communities(), 1) - community := response.Communities[0] + community := response.Communities()[0] orgChat := &protobuf.CommunityChat{ Permissions: &protobuf.CommunityPermissions{ @@ -173,13 +171,13 @@ func (s *MessengerCommunitiesSuite) TestJoinCommunity() { Description: "status-core community chat", }, } - response, err = s.bob.CreateCommunityChat(community.IDString(), orgChat) + response, err = s.bob.CreateCommunityChat(community.ID(), orgChat) s.Require().NoError(err) s.Require().NotNil(response) - s.Require().Len(response.Communities, 1) - s.Require().Len(response.Chats, 1) + s.Require().Len(response.Communities(), 1) + s.Require().Len(response.Chats(), 1) - createdChat := response.Chats[0] + createdChat := response.Chats()[0] s.Require().Equal(community.IDString(), createdChat.CommunityID) s.Require().Equal(orgChat.Identity.DisplayName, createdChat.Name) s.Require().NotEmpty(createdChat.ID) @@ -189,7 +187,7 @@ func (s *MessengerCommunitiesSuite) TestJoinCommunity() { s.Require().True(strings.HasPrefix(createdChat.ID, community.IDString())) // Make sure the changes are reflect in the community - community = response.Communities[0] + community = response.Communities()[0] chats := community.Chats() s.Require().Len(chats, 1) @@ -201,7 +199,7 @@ func (s *MessengerCommunitiesSuite) TestJoinCommunity() { inputMessage.Text = "some text" inputMessage.CommunityID = community.IDString() - err = s.bob.SaveChat(&chat) + err = s.bob.SaveChat(chat) s.NoError(err) _, err = s.bob.SendChatMessage(context.Background(), inputMessage) s.NoError(err) @@ -212,7 +210,7 @@ func (s *MessengerCommunitiesSuite) TestJoinCommunity() { if err != nil { return err } - if len(response.Communities) == 0 { + if len(response.Communities()) == 0 { return errors.New("community not received") } return nil @@ -222,20 +220,20 @@ func (s *MessengerCommunitiesSuite) TestJoinCommunity() { communities, err := s.alice.Communities() s.Require().NoError(err) s.Require().Len(communities, 2) - s.Require().Len(response.Communities, 1) + s.Require().Len(response.Communities(), 1) s.Require().Len(response.Messages, 1) s.Require().Equal(community.IDString(), response.Messages[0].CommunityID) // We join the org - response, err = s.alice.JoinCommunity(community.IDString()) + response, err = s.alice.JoinCommunity(community.ID()) s.Require().NoError(err) s.Require().NotNil(response) - s.Require().Len(response.Communities, 1) - s.Require().True(response.Communities[0].Joined()) - s.Require().Len(response.Chats, 1) + s.Require().Len(response.Communities(), 1) + s.Require().True(response.Communities()[0].Joined()) + s.Require().Len(response.Chats(), 1) // The chat should be created - createdChat = response.Chats[0] + createdChat = response.Chats()[0] s.Require().Equal(community.IDString(), createdChat.CommunityID) s.Require().Equal(orgChat.Identity.DisplayName, createdChat.Name) s.Require().NotEmpty(createdChat.ID) @@ -254,11 +252,11 @@ func (s *MessengerCommunitiesSuite) TestJoinCommunity() { Description: "status-core-ui community chat", }, } - response, err = s.bob.CreateCommunityChat(community.IDString(), orgChat) + response, err = s.bob.CreateCommunityChat(community.ID(), orgChat) s.Require().NoError(err) s.Require().NotNil(response) - s.Require().Len(response.Communities, 1) - s.Require().Len(response.Chats, 1) + s.Require().Len(response.Communities(), 1) + s.Require().Len(response.Chats(), 1) // Pull message, this time it should be received as advertised automatically err = tt.RetryWithBackOff(func() error { @@ -266,7 +264,7 @@ func (s *MessengerCommunitiesSuite) TestJoinCommunity() { if err != nil { return err } - if len(response.Communities) == 0 { + if len(response.Communities()) == 0 { return errors.New("community not received") } return nil @@ -276,11 +274,11 @@ func (s *MessengerCommunitiesSuite) TestJoinCommunity() { communities, err = s.alice.Communities() s.Require().NoError(err) s.Require().Len(communities, 2) - s.Require().Len(response.Communities, 1) - s.Require().Len(response.Chats, 1) + s.Require().Len(response.Communities(), 1) + s.Require().Len(response.Chats(), 1) // The chat should be created - createdChat = response.Chats[0] + createdChat = response.Chats()[0] s.Require().Equal(community.IDString(), createdChat.CommunityID) s.Require().Equal(orgChat.Identity.DisplayName, createdChat.Name) s.Require().NotEmpty(createdChat.ID) @@ -290,39 +288,41 @@ func (s *MessengerCommunitiesSuite) TestJoinCommunity() { s.Require().True(strings.HasPrefix(createdChat.ID, community.IDString())) // We leave the org - response, err = s.alice.LeaveCommunity(community.IDString()) + response, err = s.alice.LeaveCommunity(community.ID()) s.Require().NoError(err) s.Require().NotNil(response) - s.Require().Len(response.Communities, 1) - s.Require().False(response.Communities[0].Joined()) - s.Require().Len(response.RemovedChats, 2) + s.Require().Len(response.Communities(), 1) + s.Require().False(response.Communities()[0].Joined()) + s.Require().Len(response.RemovedChats(), 2) } -func (s *MessengerCommunitiesSuite) TestInviteUserToCommunity() { - description := &protobuf.CommunityDescription{ - Permissions: &protobuf.CommunityPermissions{ - Access: protobuf.CommunityPermissions_NO_MEMBERSHIP, - }, - Identity: &protobuf.ChatIdentity{ - DisplayName: "status", - Description: "status community description", - }, +func (s *MessengerCommunitiesSuite) TestInviteUsersToCommunity() { + description := &requests.CreateCommunity{ + Membership: protobuf.CommunityPermissions_NO_MEMBERSHIP, + Name: "status", + Color: "#ffffff", + Description: "status community description", } // Create an community chat response, err := s.bob.CreateCommunity(description) s.Require().NoError(err) s.Require().NotNil(response) - s.Require().Len(response.Communities, 1) + s.Require().Len(response.Communities(), 1) - community := response.Communities[0] + community := response.Communities()[0] - response, err = s.bob.InviteUserToCommunity(community.IDString(), common.PubkeyToHex(&s.alice.identity.PublicKey)) + response, err = s.bob.InviteUsersToCommunity( + &requests.InviteUsersToCommunity{ + CommunityID: community.ID(), + Users: []types.HexBytes{common.PubkeyToHexBytes(&s.alice.identity.PublicKey)}, + }, + ) s.Require().NoError(err) s.Require().NotNil(response) - s.Require().Len(response.Communities, 1) + s.Require().Len(response.Communities(), 1) - community = response.Communities[0] + community = response.Communities()[0] s.Require().True(community.HasMember(&s.alice.identity.PublicKey)) // Pull message and make sure org is received @@ -331,7 +331,7 @@ func (s *MessengerCommunitiesSuite) TestInviteUserToCommunity() { if err != nil { return err } - if len(response.Communities) == 0 { + if len(response.Communities()) == 0 { return errors.New("community not received") } return nil @@ -341,30 +341,27 @@ func (s *MessengerCommunitiesSuite) TestInviteUserToCommunity() { communities, err := s.alice.Communities() s.Require().NoError(err) s.Require().Len(communities, 2) - s.Require().Len(response.Communities, 1) + s.Require().Len(response.Communities(), 1) - community = response.Communities[0] + community = response.Communities()[0] s.Require().True(community.HasMember(&s.alice.identity.PublicKey)) } func (s *MessengerCommunitiesSuite) TestPostToCommunityChat() { - description := &protobuf.CommunityDescription{ - Permissions: &protobuf.CommunityPermissions{ - Access: protobuf.CommunityPermissions_INVITATION_ONLY, - }, - Identity: &protobuf.ChatIdentity{ - DisplayName: "status", - Description: "status community description", - }, + description := &requests.CreateCommunity{ + Membership: protobuf.CommunityPermissions_INVITATION_ONLY, + Name: "status", + Color: "#ffffff", + Description: "status community description", } // Create an community chat response, err := s.bob.CreateCommunity(description) s.Require().NoError(err) s.Require().NotNil(response) - s.Require().Len(response.Communities, 1) + s.Require().Len(response.Communities(), 1) - community := response.Communities[0] + community := response.Communities()[0] // Create chat orgChat := &protobuf.CommunityChat{ @@ -377,18 +374,23 @@ func (s *MessengerCommunitiesSuite) TestPostToCommunityChat() { }, } - response, err = s.bob.CreateCommunityChat(community.IDString(), orgChat) + response, err = s.bob.CreateCommunityChat(community.ID(), orgChat) s.Require().NoError(err) s.Require().NotNil(response) - s.Require().Len(response.Communities, 1) - s.Require().Len(response.Chats, 1) + s.Require().Len(response.Communities(), 1) + s.Require().Len(response.Chats(), 1) - response, err = s.bob.InviteUserToCommunity(community.IDString(), common.PubkeyToHex(&s.alice.identity.PublicKey)) + response, err = s.bob.InviteUsersToCommunity( + &requests.InviteUsersToCommunity{ + CommunityID: community.ID(), + Users: []types.HexBytes{common.PubkeyToHexBytes(&s.alice.identity.PublicKey)}, + }, + ) s.Require().NoError(err) s.Require().NotNil(response) - s.Require().Len(response.Communities, 1) + s.Require().Len(response.Communities(), 1) - community = response.Communities[0] + community = response.Communities()[0] s.Require().True(community.HasMember(&s.alice.identity.PublicKey)) // Pull message and make sure org is received @@ -397,7 +399,7 @@ func (s *MessengerCommunitiesSuite) TestPostToCommunityChat() { if err != nil { return err } - if len(response.Communities) == 0 { + if len(response.Communities()) == 0 { return errors.New("community not received") } return nil @@ -407,29 +409,29 @@ func (s *MessengerCommunitiesSuite) TestPostToCommunityChat() { communities, err := s.alice.Communities() s.Require().NoError(err) s.Require().Len(communities, 2) - s.Require().Len(response.Communities, 1) + s.Require().Len(response.Communities(), 1) // We join the org - response, err = s.alice.JoinCommunity(community.IDString()) + response, err = s.alice.JoinCommunity(community.ID()) s.Require().NoError(err) s.Require().NotNil(response) - s.Require().Len(response.Communities, 1) - s.Require().True(response.Communities[0].Joined()) - s.Require().Len(response.Chats, 1) + s.Require().Len(response.Communities(), 1) + s.Require().True(response.Communities()[0].Joined()) + s.Require().Len(response.Chats(), 1) s.Require().Len(response.Filters, 2) var orgFilterFound bool var chatFilterFound bool for _, f := range response.Filters { - orgFilterFound = orgFilterFound || f.ChatID == response.Communities[0].IDString() - chatFilterFound = chatFilterFound || f.ChatID == response.Chats[0].ID + orgFilterFound = orgFilterFound || f.ChatID == response.Communities()[0].IDString() + chatFilterFound = chatFilterFound || f.ChatID == response.Chats()[0].ID } // Make sure an community filter has been created s.Require().True(orgFilterFound) // Make sure the chat filter has been created s.Require().True(chatFilterFound) - chatID := response.Chats[0].ID + chatID := response.Chats()[0].ID inputMessage := &common.Message{} inputMessage.ChatId = chatID inputMessage.ContentType = protobuf.ChatMessage_TEXT_PLAIN @@ -452,32 +454,27 @@ func (s *MessengerCommunitiesSuite) TestPostToCommunityChat() { s.Require().NoError(err) s.Require().Len(response.Messages, 1) - s.Require().Len(response.Chats, 1) - s.Require().Equal(chatID, response.Chats[0].ID) + s.Require().Len(response.Chats(), 1) + s.Require().Equal(chatID, response.Chats()[0].ID) } func (s *MessengerCommunitiesSuite) TestImportCommunity() { - description := &protobuf.CommunityDescription{ - Permissions: &protobuf.CommunityPermissions{ - Access: protobuf.CommunityPermissions_NO_MEMBERSHIP, - }, - Identity: &protobuf.ChatIdentity{ - DisplayName: "status", - Description: "status community description", - }, + description := &requests.CreateCommunity{ + Membership: protobuf.CommunityPermissions_NO_MEMBERSHIP, + Name: "status", + Color: "#ffffff", + Description: "status community description", } // Create an community chat response, err := s.bob.CreateCommunity(description) s.Require().NoError(err) s.Require().NotNil(response) - s.Require().Len(response.Communities, 1) + s.Require().Len(response.Communities(), 1) - s.bob.logger.Info("communitise", zap.Any("COMM", response.Communities)) + community := response.Communities()[0] - community := response.Communities[0] - - privateKey, err := s.bob.ExportCommunity(community.IDString()) + privateKey, err := s.bob.ExportCommunity(community.ID()) s.Require().NoError(err) response, err = s.alice.ImportCommunity(privateKey) @@ -488,7 +485,12 @@ func (s *MessengerCommunitiesSuite) TestImportCommunity() { newUser, err := crypto.GenerateKey() s.Require().NoError(err) - _, err = s.bob.InviteUserToCommunity(community.IDString(), common.PubkeyToHex(&newUser.PublicKey)) + _, err = s.bob.InviteUsersToCommunity( + &requests.InviteUsersToCommunity{ + CommunityID: community.ID(), + Users: []types.HexBytes{common.PubkeyToHexBytes(&newUser.PublicKey)}, + }, + ) s.Require().NoError(err) // Pull message and make sure org is received @@ -497,15 +499,468 @@ func (s *MessengerCommunitiesSuite) TestImportCommunity() { if err != nil { return err } - if len(response.Communities) == 0 { + if len(response.Communities()) == 0 { return errors.New("community not received") } return nil }) s.Require().NoError(err) - s.Require().Len(response.Communities, 1) - community = response.Communities[0] + s.Require().Len(response.Communities(), 1) + community = response.Communities()[0] s.Require().True(community.Joined()) s.Require().True(community.IsAdmin()) } + +func (s *MessengerCommunitiesSuite) TestRequestAccess() { + description := &requests.CreateCommunity{ + Membership: protobuf.CommunityPermissions_ON_REQUEST, + Name: "status", + Color: "#ffffff", + Description: "status community description", + } + + // Create an community chat + response, err := s.bob.CreateCommunity(description) + s.Require().NoError(err) + s.Require().NotNil(response) + s.Require().Len(response.Communities(), 1) + + community := response.Communities()[0] + + chat := CreateOneToOneChat(common.PubkeyToHex(&s.alice.identity.PublicKey), &s.alice.identity.PublicKey, s.alice.transport) + + s.Require().NoError(s.bob.SaveChat(chat)) + + message := buildTestMessage(*chat) + message.CommunityID = community.IDString() + + // We send a community link to alice + response, err = s.bob.SendChatMessage(context.Background(), message) + s.Require().NoError(err) + s.Require().NotNil(response) + + // Retrieve community link & community + err = tt.RetryWithBackOff(func() error { + response, err = s.alice.RetrieveAll() + if err != nil { + return err + } + if len(response.Communities()) == 0 { + return errors.New("message not received") + } + return nil + }) + + s.Require().NoError(err) + + request := &requests.RequestToJoinCommunity{CommunityID: community.ID()} + // We try to join the org + response, err = s.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(&s.alice.identity.PublicKey)) + s.Require().Equal(communities.RequestToJoinStatePending, requestToJoin1.State) + + // Make sure clock is not empty + s.Require().NotEmpty(requestToJoin1.Clock) + + s.Require().Len(response.Communities(), 1) + s.Require().Equal(response.Communities()[0].RequestedToJoinAt(), requestToJoin1.Clock) + + // pull all communities to make sure we set RequestedToJoinAt + + allCommunities, err := s.alice.Communities() + s.Require().NoError(err) + s.Require().Len(allCommunities, 2) + + if bytes.Equal(allCommunities[0].ID(), community.ID()) { + s.Require().Equal(allCommunities[0].RequestedToJoinAt(), requestToJoin1.Clock) + } else { + s.Require().Equal(allCommunities[1].RequestedToJoinAt(), requestToJoin1.Clock) + } + + // pull to make sure it has been saved + requestsToJoin, err := s.alice.MyPendingRequestsToJoin() + s.Require().NoError(err) + s.Require().Len(requestsToJoin, 1) + + // Make sure the requests are fetched also by community + requestsToJoin, err = s.alice.PendingRequestsToJoinForCommunity(community.ID()) + s.Require().NoError(err) + s.Require().Len(requestsToJoin, 1) + + // Retrieve request to join + err = tt.RetryWithBackOff(func() error { + response, err = s.bob.RetrieveAll() + if err != nil { + return err + } + if len(response.RequestsToJoinCommunity) == 0 { + return errors.New("request to join community not received") + } + return nil + }) + s.Require().NoError(err) + s.Require().Len(response.RequestsToJoinCommunity, 1) + + requestToJoin2 := response.RequestsToJoinCommunity[0] + + s.Require().NotNil(requestToJoin2) + s.Require().Equal(community.ID(), requestToJoin2.CommunityID) + s.Require().False(requestToJoin2.Our) + s.Require().NotEmpty(requestToJoin2.ID) + s.Require().NotEmpty(requestToJoin2.Clock) + s.Require().Equal(requestToJoin2.PublicKey, common.PubkeyToHex(&s.alice.identity.PublicKey)) + s.Require().Equal(communities.RequestToJoinStatePending, requestToJoin2.State) + + s.Require().Equal(requestToJoin1.ID, requestToJoin2.ID) + + // Accept request + + acceptRequestToJoin := &requests.AcceptRequestToJoinCommunity{ID: requestToJoin1.ID} + + response, err = s.bob.AcceptRequestToJoinCommunity(acceptRequestToJoin) + s.Require().NoError(err) + s.Require().NotNil(response) + + s.Require().Len(response.Communities(), 1) + + updatedCommunity := response.Communities()[0] + + s.Require().NotNil(updatedCommunity) + s.Require().True(updatedCommunity.HasMember(&s.alice.identity.PublicKey)) + + // Pull message and make sure org is received + err = tt.RetryWithBackOff(func() error { + response, err = s.alice.RetrieveAll() + if err != nil { + return err + } + if len(response.Communities()) == 0 { + return errors.New("community not received") + } + return nil + }) + + s.Require().NoError(err) + s.Require().NotNil(response) + + s.Require().Len(response.Communities(), 1) + + aliceCommunity := response.Communities()[0] + + s.Require().Equal(community.ID(), aliceCommunity.ID()) + s.Require().True(aliceCommunity.HasMember(&s.alice.identity.PublicKey)) + + // Community should be joined at this point + s.Require().True(aliceCommunity.Joined()) + + // Make sure the requests are not pending on either sides + requestsToJoin, err = s.bob.PendingRequestsToJoinForCommunity(community.ID()) + s.Require().NoError(err) + s.Require().Len(requestsToJoin, 0) + + requestsToJoin, err = s.alice.MyPendingRequestsToJoin() + s.Require().NoError(err) + s.Require().Len(requestsToJoin, 0) + +} + +func (s *MessengerCommunitiesSuite) TestRequestAccessAgain() { + description := &requests.CreateCommunity{ + Membership: protobuf.CommunityPermissions_ON_REQUEST, + Name: "status", + Color: "#ffffff", + Description: "status community description", + } + + // Create an community chat + response, err := s.bob.CreateCommunity(description) + s.Require().NoError(err) + s.Require().NotNil(response) + s.Require().Len(response.Communities(), 1) + + community := response.Communities()[0] + + chat := CreateOneToOneChat(common.PubkeyToHex(&s.alice.identity.PublicKey), &s.alice.identity.PublicKey, s.alice.transport) + + s.Require().NoError(s.bob.SaveChat(chat)) + + message := buildTestMessage(*chat) + message.CommunityID = community.IDString() + + // We send a community link to alice + response, err = s.bob.SendChatMessage(context.Background(), message) + s.Require().NoError(err) + s.Require().NotNil(response) + + // Retrieve community link & community + err = tt.RetryWithBackOff(func() error { + response, err = s.alice.RetrieveAll() + if err != nil { + return err + } + if len(response.Communities()) == 0 { + return errors.New("message not received") + } + return nil + }) + + s.Require().NoError(err) + + request := &requests.RequestToJoinCommunity{CommunityID: community.ID()} + // We try to join the org + response, err = s.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(&s.alice.identity.PublicKey)) + s.Require().Equal(communities.RequestToJoinStatePending, requestToJoin1.State) + + // Make sure clock is not empty + s.Require().NotEmpty(requestToJoin1.Clock) + + s.Require().Len(response.Communities(), 1) + s.Require().Equal(response.Communities()[0].RequestedToJoinAt(), requestToJoin1.Clock) + + // pull all communities to make sure we set RequestedToJoinAt + + allCommunities, err := s.alice.Communities() + s.Require().NoError(err) + s.Require().Len(allCommunities, 2) + + if bytes.Equal(allCommunities[0].ID(), community.ID()) { + s.Require().Equal(allCommunities[0].RequestedToJoinAt(), requestToJoin1.Clock) + } else { + s.Require().Equal(allCommunities[1].RequestedToJoinAt(), requestToJoin1.Clock) + } + + // pull to make sure it has been saved + requestsToJoin, err := s.alice.MyPendingRequestsToJoin() + s.Require().NoError(err) + s.Require().Len(requestsToJoin, 1) + + // Make sure the requests are fetched also by community + requestsToJoin, err = s.alice.PendingRequestsToJoinForCommunity(community.ID()) + s.Require().NoError(err) + s.Require().Len(requestsToJoin, 1) + + // Retrieve request to join + err = tt.RetryWithBackOff(func() error { + response, err = s.bob.RetrieveAll() + if err != nil { + return err + } + if len(response.RequestsToJoinCommunity) == 0 { + return errors.New("request to join community not received") + } + return nil + }) + s.Require().NoError(err) + s.Require().Len(response.RequestsToJoinCommunity, 1) + + requestToJoin2 := response.RequestsToJoinCommunity[0] + + s.Require().NotNil(requestToJoin2) + s.Require().Equal(community.ID(), requestToJoin2.CommunityID) + s.Require().False(requestToJoin2.Our) + s.Require().NotEmpty(requestToJoin2.ID) + s.Require().NotEmpty(requestToJoin2.Clock) + s.Require().Equal(requestToJoin2.PublicKey, common.PubkeyToHex(&s.alice.identity.PublicKey)) + s.Require().Equal(communities.RequestToJoinStatePending, requestToJoin2.State) + + s.Require().Equal(requestToJoin1.ID, requestToJoin2.ID) + + // Accept request + + acceptRequestToJoin := &requests.AcceptRequestToJoinCommunity{ID: requestToJoin1.ID} + + response, err = s.bob.AcceptRequestToJoinCommunity(acceptRequestToJoin) + s.Require().NoError(err) + s.Require().NotNil(response) + + s.Require().Len(response.Communities(), 1) + + updatedCommunity := response.Communities()[0] + + s.Require().NotNil(updatedCommunity) + s.Require().True(updatedCommunity.HasMember(&s.alice.identity.PublicKey)) + + // Pull message and make sure org is received + err = tt.RetryWithBackOff(func() error { + response, err = s.alice.RetrieveAll() + if err != nil { + return err + } + if len(response.Communities()) == 0 { + return errors.New("community not received") + } + return nil + }) + + s.Require().NoError(err) + s.Require().NotNil(response) + + s.Require().Len(response.Communities(), 1) + + aliceCommunity := response.Communities()[0] + + s.Require().Equal(community.ID(), aliceCommunity.ID()) + s.Require().True(aliceCommunity.HasMember(&s.alice.identity.PublicKey)) + + // Community should be joined at this point + s.Require().True(aliceCommunity.Joined()) + + // Make sure the requests are not pending on either sides + requestsToJoin, err = s.bob.PendingRequestsToJoinForCommunity(community.ID()) + s.Require().NoError(err) + s.Require().Len(requestsToJoin, 0) + + requestsToJoin, err = s.alice.MyPendingRequestsToJoin() + s.Require().NoError(err) + s.Require().Len(requestsToJoin, 0) + + // We request again + request2 := &requests.RequestToJoinCommunity{CommunityID: community.ID()} + // We try to join the org, it should error as we are already a member + response, err = s.alice.RequestToJoinCommunity(request2) + s.Require().Error(err) + + // We kick the member + response, err = s.bob.RemoveUserFromCommunity( + community.ID(), + common.PubkeyToHex(&s.alice.identity.PublicKey), + ) + s.Require().NoError(err) + s.Require().NotNil(response) + s.Require().Len(response.Communities(), 1) + + community = response.Communities()[0] + s.Require().False(community.HasMember(&s.alice.identity.PublicKey)) + + // Alice should then be removed + err = tt.RetryWithBackOff(func() error { + response, err = s.alice.RetrieveAll() + if err != nil { + return err + } + if len(response.Communities()) == 0 { + return errors.New("community not received") + } + return nil + }) + + s.Require().NoError(err) + s.Require().NotNil(response) + + s.Require().Len(response.Communities(), 1) + + aliceCommunity = response.Communities()[0] + + s.Require().Equal(community.ID(), aliceCommunity.ID()) + s.Require().False(aliceCommunity.HasMember(&s.alice.identity.PublicKey)) + + // Alice can request access again + request3 := &requests.RequestToJoinCommunity{CommunityID: community.ID()} + response, err = s.alice.RequestToJoinCommunity(request3) + s.Require().NoError(err) + s.Require().NotNil(response) + s.Require().Len(response.RequestsToJoinCommunity, 1) + + requestToJoin3 := response.RequestsToJoinCommunity[0] + s.Require().NotNil(requestToJoin3) + s.Require().Equal(community.ID(), requestToJoin3.CommunityID) + s.Require().True(requestToJoin3.Our) + s.Require().NotEmpty(requestToJoin3.ID) + s.Require().NotEmpty(requestToJoin3.Clock) + s.Require().Equal(requestToJoin3.PublicKey, common.PubkeyToHex(&s.alice.identity.PublicKey)) + s.Require().Equal(communities.RequestToJoinStatePending, requestToJoin3.State) + + s.Require().Len(response.Communities(), 1) + s.Require().Equal(response.Communities()[0].RequestedToJoinAt(), requestToJoin3.Clock) + + // Retrieve request to join + err = tt.RetryWithBackOff(func() error { + response, err = s.bob.RetrieveAll() + if err != nil { + return err + } + if len(response.RequestsToJoinCommunity) == 0 { + return errors.New("request to join community not received") + } + return nil + }) + s.Require().NoError(err) + s.Require().Len(response.RequestsToJoinCommunity, 1) + + requestToJoin4 := response.RequestsToJoinCommunity[0] + + s.Require().NotNil(requestToJoin4) + s.Require().Equal(community.ID(), requestToJoin4.CommunityID) + s.Require().False(requestToJoin4.Our) + s.Require().NotEmpty(requestToJoin4.ID) + s.Require().NotEmpty(requestToJoin4.Clock) + s.Require().Equal(requestToJoin4.PublicKey, common.PubkeyToHex(&s.alice.identity.PublicKey)) + s.Require().Equal(communities.RequestToJoinStatePending, requestToJoin4.State) + + s.Require().Equal(requestToJoin3.ID, requestToJoin4.ID) +} + +func (s *MessengerCommunitiesSuite) TestShareCommunity() { + description := &requests.CreateCommunity{ + Membership: protobuf.CommunityPermissions_NO_MEMBERSHIP, + Name: "status", + Color: "#ffffff", + Description: "status community description", + } + + // Create an community chat + response, err := s.bob.CreateCommunity(description) + s.Require().NoError(err) + s.Require().NotNil(response) + s.Require().Len(response.Communities(), 1) + + community := response.Communities()[0] + + response, err = s.bob.ShareCommunity( + &requests.ShareCommunity{ + CommunityID: community.ID(), + Users: []types.HexBytes{common.PubkeyToHexBytes(&s.alice.identity.PublicKey)}, + }, + ) + s.Require().NoError(err) + s.Require().NotNil(response) + s.Require().Len(response.Messages, 1) + + // Pull message and make sure org is received + err = tt.RetryWithBackOff(func() error { + response, err = s.alice.RetrieveAll() + if err != nil { + return err + } + if len(response.Messages) == 0 { + return errors.New("community link not received") + } + return nil + }) + + s.Require().NoError(err) + s.Require().Len(response.Messages, 1) + +} diff --git a/protocol/contact.go b/protocol/contact.go index cd961cfb9..269a51969 100644 --- a/protocol/contact.go +++ b/protocol/contact.go @@ -37,13 +37,6 @@ type Contact struct { Name string `json:"name,omitempty"` // EnsVerified whether we verified the name of the contact ENSVerified bool `json:"ensVerified"` - // EnsVerifiedAt the time we last verified the name - ENSVerifiedAt uint64 `json:"ensVerifiedAt"` - // LastENSClockValue is the last clock value of when we - // received an ENS name for the user - LastENSClockValue uint64 `json:"lastENSClockValue"` - // ENSVerificationRetries is how many times we retried the ENS - ENSVerificationRetries uint64 `json:"ensVerificationRetries"` // Generated username name of the contact Alias string `json:"alias,omitempty"` // Identicon generated from public key @@ -56,7 +49,6 @@ type Contact struct { SystemTags []string `json:"systemTags"` DeviceInfo []ContactDeviceInfo `json:"deviceInfo"` - TributeToTalk string `json:"tributeToTalk,omitempty"` LocalNickname string `json:"localNickname,omitempty"` Images map[string]images.IdentityImage `json:"images"` @@ -94,14 +86,6 @@ func (c *Contact) Remove() { c.SystemTags = newSystemTags } -func (c *Contact) ResetENSVerification(clock uint64, name string) { - c.ENSVerifiedAt = 0 - c.ENSVerified = false - c.ENSVerificationRetries = 0 - c.LastENSClockValue = clock - c.Name = name -} - // existsInStringSlice checks if a string is in a set. func existsInStringSlice(set []string, find string) bool { for _, s := range set { diff --git a/protocol/ens.go b/protocol/ens.go deleted file mode 100644 index a39340fea..000000000 --- a/protocol/ens.go +++ /dev/null @@ -1,63 +0,0 @@ -package protocol - -import ( - "math" - "strings" -) - -// maxRetries is the maximum number of attempts we do before giving up -const maxRetries uint64 = 11 - -// ENSBackoffTimeSec is the step of the exponential backoff -// we retry roughly for 17 hours after receiving the message 2^11 * 30 -const ENSBackoffTimeSec uint64 = 30 - -// We calculate if it's too early to retry, by exponentially backing off -func verifiedENSRecentlyEnough(now, verifiedAt, retries uint64) bool { - return now < verifiedAt+ENSBackoffTimeSec*retries*uint64(math.Exp2(float64(retries))) -} - -func shouldENSBeVerified(c *Contact, now uint64) bool { - if c.Name == "" { - return false - } - - if c.ENSVerified { - return false - } - - if c.ENSVerificationRetries >= maxRetries { - return false - } - - if verifiedENSRecentlyEnough(now, c.ENSVerifiedAt, c.ENSVerificationRetries) { - return false - } - - if !strings.HasSuffix(c.Name, ".eth") { - return false - } - - return true -} - -// This should trigger re-verification of the ENS name for this contact -func hasENSNameChanged(c *Contact, newName string, clockValue uint64) bool { - if c.LastENSClockValue > clockValue { - return false - } - - if newName == "" { - return false - } - - if !strings.HasSuffix(newName, ".eth") { - return false - } - - if newName == c.Name { - return false - } - - return true -} diff --git a/protocol/ens/const.go b/protocol/ens/const.go new file mode 100644 index 000000000..d2b810267 --- /dev/null +++ b/protocol/ens/const.go @@ -0,0 +1,8 @@ +package ens + +// maxRetries is the maximum number of attempts we do before giving up +const maxRetries uint64 = 11 + +// ENSBackoffTimeSec is the step of the exponential backoff +// we retry roughly for 17 hours after receiving the message 2^11 * 30 +const ENSBackoffTimeSec uint64 = 30 diff --git a/protocol/ens/persistence.go b/protocol/ens/persistence.go new file mode 100644 index 000000000..1ad97cb25 --- /dev/null +++ b/protocol/ens/persistence.go @@ -0,0 +1,105 @@ +package ens + +import ( + "context" + "database/sql" + "errors" +) + +type Persistence struct { + db *sql.DB +} + +func NewPersistence(db *sql.DB) *Persistence { + return &Persistence{db: db} +} + +func (p *Persistence) GetENSToBeVerified(now uint64) ([]*VerificationRecord, error) { + rows, err := p.db.Query(`SELECT public_key, name, verified, verified_at, clock, verification_retries, next_retry FROM ens_verification_records WHERE NOT(verified) AND verification_retries < ? AND next_retry <= ?`, maxRetries, now) + if err != nil { + return nil, err + } + + var records []*VerificationRecord + for rows.Next() { + var record VerificationRecord + err := rows.Scan(&record.PublicKey, &record.Name, &record.Verified, &record.VerifiedAt, &record.Clock, &record.VerificationRetries, &record.NextRetry) + if err != nil { + return nil, err + } + records = append(records, &record) + } + + return records, nil +} + +func (p *Persistence) UpdateRecords(records []*VerificationRecord) (err error) { + var tx *sql.Tx + tx, err = p.db.BeginTx(context.Background(), &sql.TxOptions{}) + if err != nil { + return err + } + + defer func() { + if err == nil { + err = tx.Commit() + return + } + // don't shadow original error + _ = tx.Rollback() + }() + + for _, record := range records { + var stmt *sql.Stmt + stmt, err = tx.Prepare(`UPDATE ens_verification_records SET verified = ?, verified_at = ?, verification_retries = ?, next_retry = ? WHERE public_key = ?`) + if err != nil { + return err + } + defer stmt.Close() + _, err = stmt.Exec(record.Verified, record.VerifiedAt, record.VerificationRetries, record.NextRetry, record.PublicKey) + if err != nil { + return err + } + + } + + return nil +} + +// AddRecord adds a record or return the latest available if already in the database and +// hasn't changed +func (p *Persistence) AddRecord(record VerificationRecord) (response *VerificationRecord, err error) { + if !record.Valid() { + err = errors.New("invalid ens record") + return + } + var tx *sql.Tx + tx, err = p.db.BeginTx(context.Background(), &sql.TxOptions{}) + if err != nil { + return + } + + defer func() { + if err == nil { + err = tx.Commit() + return + } + // don't shadow original error + _ = tx.Rollback() + }() + + dbRecord := &VerificationRecord{PublicKey: record.PublicKey} + + err = tx.QueryRow(`SELECT name, clock, verified FROM ens_verification_records WHERE public_key = ?`, record.PublicKey).Scan(&dbRecord.Name, &dbRecord.Clock, &dbRecord.Verified) + if err != nil && err != sql.ErrNoRows { + return + } + + if dbRecord.Clock >= record.Clock || dbRecord.Name == record.Name { + response = dbRecord + return + } + + _, err = tx.Exec(`INSERT INTO ens_verification_records(public_key, name, clock) VALUES (?,?,?)`, record.PublicKey, record.Name, record.Clock) + return +} diff --git a/protocol/ens/persistence_test.go b/protocol/ens/persistence_test.go new file mode 100644 index 000000000..20004e5d3 --- /dev/null +++ b/protocol/ens/persistence_test.go @@ -0,0 +1,88 @@ +package ens + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/status-im/status-go/protocol/sqlite" +) + +func TestGetENSToBeVerified(t *testing.T) { + pk := "1" + name := "test.eth" + updatedName := "test2.eth" + + db, err := sqlite.Open(sqlite.InMemoryPath, "") + require.NoError(t, err) + + err = sqlite.Migrate(db) + require.NoError(t, err) + + persistence := NewPersistence(db) + require.NotNil(t, persistence) + + record := VerificationRecord{Name: name, PublicKey: pk, Clock: 2} + + // We add a record, it should be nil + response, err := persistence.AddRecord(record) + require.NoError(t, err) + require.Nil(t, response) + + // We add a record again, it should return the same record + response, err = persistence.AddRecord(record) + require.NoError(t, err) + require.NotNil(t, response) + + require.False(t, response.Verified) + require.Equal(t, record.Name, response.Name) + require.Equal(t, record.PublicKey, response.PublicKey) + + // We add a record again, with a different clock value + record.Clock++ + response, err = persistence.AddRecord(record) + require.NoError(t, err) + require.NotNil(t, response) + + require.False(t, response.Verified) + require.Equal(t, record.Name, response.Name) + require.Equal(t, record.PublicKey, response.PublicKey) + + // We add a record again, with a different name, but lower clock value + record.Clock-- + record.Name = updatedName + response, err = persistence.AddRecord(record) + require.NoError(t, err) + require.NotNil(t, response) + + require.False(t, response.Verified) + require.Equal(t, name, response.Name) + require.Equal(t, record.PublicKey, response.PublicKey) + + // We add a record again, with a different name and higher clock value + record.Clock += 2 + record.Name = updatedName + response, err = persistence.AddRecord(record) + require.NoError(t, err) + require.Nil(t, response) + + // update the record + + record.Verified = false + record.VerificationRetries = 10 + record.NextRetry = 20 + record.VerifiedAt = 30 + + err = persistence.UpdateRecords([]*VerificationRecord{&record}) + require.NoError(t, err) + + toBeVerified, err := persistence.GetENSToBeVerified(20) + require.NoError(t, err) + require.Len(t, toBeVerified, 1) + require.False(t, toBeVerified[0].Verified) + require.Equal(t, uint64(10), toBeVerified[0].VerificationRetries) + require.Equal(t, uint64(20), toBeVerified[0].NextRetry) + require.Equal(t, uint64(30), toBeVerified[0].VerifiedAt) + require.Equal(t, updatedName, toBeVerified[0].Name) + require.Equal(t, pk, toBeVerified[0].PublicKey) +} diff --git a/protocol/ens/record.go b/protocol/ens/record.go new file mode 100644 index 000000000..ea2e97273 --- /dev/null +++ b/protocol/ens/record.go @@ -0,0 +1,25 @@ +package ens + +import ( + "math" + "strings" +) + +type VerificationRecord struct { + PublicKey string + Name string + Clock uint64 + Verified bool + VerifiedAt uint64 + VerificationRetries uint64 + NextRetry uint64 +} + +// We calculate if it's too early to retry, by exponentially backing off +func (e *VerificationRecord) CalculateNextRetry() { + e.NextRetry = e.VerifiedAt + ENSBackoffTimeSec*uint64(math.Exp2(float64(e.VerificationRetries))) +} + +func (e *VerificationRecord) Valid() bool { + return e.Name != "" && strings.HasSuffix(e.Name, ".eth") && e.Clock > 0 +} diff --git a/protocol/ens/record_test.go b/protocol/ens/record_test.go new file mode 100644 index 000000000..8d9ca9a75 --- /dev/null +++ b/protocol/ens/record_test.go @@ -0,0 +1,26 @@ +package ens + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestNextRetry(t *testing.T) { + record := VerificationRecord{Name: "vitalik.eth"} + record.VerifiedAt = 10 + record.CalculateNextRetry() + + var expectedNextRetry uint64 = 30 + 10 + require.Equal(t, expectedNextRetry, record.NextRetry) + + expectedNextRetry = 60 + 10 + record.VerificationRetries++ + record.CalculateNextRetry() + require.Equal(t, expectedNextRetry, record.NextRetry) + + expectedNextRetry = 120 + 10 + record.VerificationRetries++ + record.CalculateNextRetry() + require.Equal(t, expectedNextRetry, record.NextRetry) +} diff --git a/protocol/ens/verifier.go b/protocol/ens/verifier.go new file mode 100644 index 000000000..80b2ae3f4 --- /dev/null +++ b/protocol/ens/verifier.go @@ -0,0 +1,194 @@ +package ens + +import ( + "database/sql" + "time" + + "go.uber.org/zap" + + "github.com/status-im/status-go/eth-node/types" + enstypes "github.com/status-im/status-go/eth-node/types/ens" + "github.com/status-im/status-go/protocol/common" +) + +type Verifier struct { + node types.Node + online bool + persistence *Persistence + logger *zap.Logger + timesource common.TimeSource + subscriptions []chan []*VerificationRecord + rpcEndpoint string + contractAddress string + quit chan struct{} +} + +func New(node types.Node, logger *zap.Logger, timesource common.TimeSource, db *sql.DB, rpcEndpoint, contractAddress string) *Verifier { + persistence := NewPersistence(db) + return &Verifier{ + node: node, + logger: logger, + persistence: persistence, + timesource: timesource, + rpcEndpoint: rpcEndpoint, + contractAddress: contractAddress, + quit: make(chan struct{}), + } +} + +func (v *Verifier) Start() error { + go v.verifyLoop() + return nil +} + +func (v *Verifier) Stop() error { + close(v.quit) + + return nil +} + +// ENSVerified adds an already verified entry to the ens table +func (v *Verifier) ENSVerified(pk, ensName string, clock uint64) error { + + // Add returns nil if no record was available + oldRecord, err := v.Add(pk, ensName, clock) + if err != nil { + return err + } + + var record *VerificationRecord + + if oldRecord != nil { + record = oldRecord + } else { + record = &VerificationRecord{PublicKey: pk, Name: ensName, Clock: clock} + } + + record.VerifiedAt = clock + record.Verified = true + records := []*VerificationRecord{record} + err = v.persistence.UpdateRecords(records) + if err != nil { + return err + } + v.publish(records) + return nil +} + +func (v *Verifier) Add(pk, ensName string, clock uint64) (*VerificationRecord, error) { + record := VerificationRecord{PublicKey: pk, Name: ensName, Clock: clock} + return v.persistence.AddRecord(record) +} + +func (v *Verifier) SetOnline(online bool) { + v.online = online +} + +func (v *Verifier) verifyLoop() { + + ticker := time.NewTicker(30 * time.Second) + for { + select { + + case <-v.quit: + ticker.Stop() + return + case <-ticker.C: + if !v.online || v.rpcEndpoint == "" || v.contractAddress == "" { + continue + } + err := v.verify(v.rpcEndpoint, v.contractAddress) + if err != nil { + v.logger.Error("verify loop failed", zap.Error(err)) + } + + } + } +} + +func (v *Verifier) Subscribe() chan []*VerificationRecord { + c := make(chan []*VerificationRecord) + v.subscriptions = append(v.subscriptions, c) + return c +} + +func (v *Verifier) publish(records []*VerificationRecord) { + v.logger.Info("publishing records", zap.Any("records", records)) + // Publish on channels, drop if buffer is full + for _, s := range v.subscriptions { + select { + case s <- records: + default: + v.logger.Warn("ens subscription channel full, dropping message") + } + } + +} + +// Verify verifies that a registered ENS name matches the expected public key +func (v *Verifier) verify(rpcEndpoint, contractAddress string) error { + v.logger.Debug("verifying ENS Names", zap.String("endpoint", rpcEndpoint)) + verifier := v.node.NewENSVerifier(v.logger) + + var ensDetails []enstypes.ENSDetails + + // Now in seconds + now := v.timesource.GetCurrentTime() / 1000 + ensToBeVerified, err := v.persistence.GetENSToBeVerified(now) + if err != nil { + return err + } + + recordsMap := make(map[string]*VerificationRecord) + + for _, r := range ensToBeVerified { + recordsMap[r.PublicKey] = r + ensDetails = append(ensDetails, enstypes.ENSDetails{ + PublicKeyString: r.PublicKey[2:], + Name: r.Name, + }) + v.logger.Debug("verifying ens name", zap.Any("record", r)) + } + + ensResponse, err := verifier.CheckBatch(ensDetails, rpcEndpoint, contractAddress) + if err != nil { + v.logger.Error("failed to check batch", zap.Error(err)) + return err + } + + var records []*VerificationRecord + + for _, details := range ensResponse { + pk := "0x" + details.PublicKeyString + record := recordsMap[pk] + + if details.Error == nil { + record.Verified = details.Verified + if !record.Verified { + record.VerificationRetries++ + } + } else { + v.logger.Warn("Failed to resolve ens name", + zap.String("name", details.Name), + zap.String("publicKey", details.PublicKeyString), + zap.Error(details.Error), + ) + record.VerificationRetries++ + } + record.VerifiedAt = now + record.CalculateNextRetry() + + records = append(records, record) + } + + err = v.persistence.UpdateRecords(records) + if err != nil { + + v.logger.Error("failed to update records", zap.Error(err)) + return err + } + + v.publish(records) + + return nil +} diff --git a/protocol/ens_test.go b/protocol/ens_test.go deleted file mode 100644 index 86f646ecf..000000000 --- a/protocol/ens_test.go +++ /dev/null @@ -1,140 +0,0 @@ -package protocol - -import ( - "testing" - - "github.com/stretchr/testify/suite" -) - -type ENSSuite struct { - suite.Suite -} - -func TestENSSuite(t *testing.T) { - suite.Run(t, new(ENSSuite)) -} - -func (s *ENSSuite) TestShouldBeVerified() { - testCases := []struct { - Name string - Contact Contact - Expected bool - TimeNow uint64 - }{ - { - Name: "valid eth name", - Contact: Contact{Name: "vitalik.eth"}, - Expected: true, - }, - { - Name: "valid eth name,some retries", - Contact: Contact{ - Name: "vitalik.eth", - ENSVerifiedAt: 10, - ENSVerificationRetries: 4, - }, - Expected: true, - TimeNow: 10 + ENSBackoffTimeSec*4*16 + 1, - }, - { - Name: "Empty name", - Contact: Contact{}, - Expected: false, - }, - { - Name: "invalid eth name", - Contact: Contact{Name: "vitalik.eth2"}, - Expected: false, - }, - { - Name: "Already verified", - Contact: Contact{ - Name: "vitalik.eth", - ENSVerified: true, - }, - Expected: false, - }, - - { - Name: "verified recently", - Contact: Contact{ - Name: "vitalik.eth", - ENSVerifiedAt: 10, - ENSVerificationRetries: 4, - }, - Expected: false, - TimeNow: 10 + ENSBackoffTimeSec*4*16 - 1, - }, - { - Name: "max retries reached", - Contact: Contact{ - Name: "vitalik.eth", - ENSVerifiedAt: 10, - ENSVerificationRetries: 11, - }, - Expected: false, - TimeNow: 10 + ENSBackoffTimeSec*5*2048 + 1, - }, - } - for _, tc := range testCases { - s.Run(tc.Name, func() { - response := shouldENSBeVerified(&tc.Contact, tc.TimeNow) - s.Equal(tc.Expected, response) - }) - } -} - -func (s *ENSSuite) TestHasENSNameChanged() { - testCases := []struct { - Name string - Contact Contact - NewName string - Clock uint64 - Expected bool - }{ - { - Name: "clock value is greater", - Contact: Contact{LastENSClockValue: 0}, - Clock: 1, - NewName: "vitalik.eth", - Expected: true, - }, - { - Name: "name in empty", - Contact: Contact{LastENSClockValue: 0}, - Clock: 1, - Expected: false, - }, - { - Name: "name is invalid", - Contact: Contact{LastENSClockValue: 0}, - Clock: 1, - NewName: "vitalik.eth2", - Expected: false, - }, - { - Name: "name is identical", - Contact: Contact{ - Name: "vitalik.eth", - LastENSClockValue: 0, - }, - Clock: 1, - NewName: "vitalik.eth", - Expected: false, - }, - - { - Name: "clock value is less", - Contact: Contact{LastENSClockValue: 1}, - Clock: 0, - NewName: "vitalik.eth", - Expected: false, - }, - } - for _, tc := range testCases { - s.Run(tc.Name, func() { - response := hasENSNameChanged(&tc.Contact, tc.NewName, tc.Clock) - s.Equal(tc.Expected, response) - }) - } -} diff --git a/protocol/message_handler.go b/protocol/message_handler.go index 443174a4e..e950d8e0d 100644 --- a/protocol/message_handler.go +++ b/protocol/message_handler.go @@ -14,6 +14,7 @@ import ( "github.com/status-im/status-go/protocol/common" "github.com/status-im/status-go/protocol/communities" "github.com/status-im/status-go/protocol/encryption/multidevice" + "github.com/status-im/status-go/protocol/ens" "github.com/status-im/status-go/protocol/protobuf" "github.com/status-im/status-go/protocol/transport" v1protocol "github.com/status-im/status-go/protocol/v1" @@ -31,15 +32,17 @@ type MessageHandler struct { identity *ecdsa.PrivateKey persistence *sqlitePersistence transport transport.Transport + ensVerifier *ens.Verifier communitiesManager *communities.Manager logger *zap.Logger } -func newMessageHandler(identity *ecdsa.PrivateKey, logger *zap.Logger, persistence *sqlitePersistence, communitiesManager *communities.Manager, transport transport.Transport) *MessageHandler { +func newMessageHandler(identity *ecdsa.PrivateKey, logger *zap.Logger, persistence *sqlitePersistence, communitiesManager *communities.Manager, transport transport.Transport, ensVerifier *ens.Verifier) *MessageHandler { return &MessageHandler{ identity: identity, persistence: persistence, communitiesManager: communitiesManager, + ensVerifier: ensVerifier, transport: transport, logger: logger} } @@ -141,8 +144,7 @@ func (m *MessageHandler) HandleMembershipUpdate(messageState *ReceivedMessageSta // Store in chats map as it might be a new one messageState.AllChats[chat.ID] = chat - // Set in the map - messageState.ModifiedChats[chat.ID] = true + messageState.Response.AddChat(chat) if message.Message != nil { messageState.CurrentMessageState.Message = *message.Message @@ -202,7 +204,7 @@ func (m *MessageHandler) handleCommandMessage(state *ReceivedMessageState, messa // Set chat active chat.Active = true // Set in the modified maps chat - state.ModifiedChats[chat.ID] = true + state.Response.AddChat(chat) state.AllChats[chat.ID] = chat // Add to response @@ -258,8 +260,8 @@ func (m *MessageHandler) HandleSyncInstallationPublicChat(state *ReceivedMessage chat := CreatePublicChat(chatID, state.Timesource) - state.AllChats[chat.ID] = &chat - state.ModifiedChats[chat.ID] = true + state.AllChats[chat.ID] = chat + state.Response.AddChat(chat) return true } @@ -294,7 +296,7 @@ func (m *MessageHandler) HandleContactUpdate(state *ReceivedMessageState, messag chat.LastClockValue = message.Clock } - state.ModifiedChats[chat.ID] = true + state.Response.AddChat(chat) state.AllChats[chat.ID] = chat return nil @@ -326,12 +328,15 @@ func (m *MessageHandler) HandlePairInstallation(state *ReceivedMessageState, mes // HandleCommunityDescription handles an community description func (m *MessageHandler) HandleCommunityDescription(state *ReceivedMessageState, signer *ecdsa.PublicKey, description protobuf.CommunityDescription, rawPayload []byte) error { - community, err := m.communitiesManager.HandleCommunityDescriptionMessage(signer, &description, rawPayload) + communityResponse, err := m.communitiesManager.HandleCommunityDescriptionMessage(signer, &description, rawPayload) if err != nil { return err } - state.AllCommunities[community.IDString()] = community + community := communityResponse.Community + + state.Response.AddCommunity(community) + state.Response.CommunityChanges = append(state.Response.CommunityChanges, communityResponse.Changes) // If we haven't joined the org, nothing to do if !community.Joined() { @@ -347,14 +352,14 @@ func (m *MessageHandler) HandleCommunityDescription(state *ReceivedMessageState, oldChat, ok := state.AllChats[chat.ID] if !ok { // Beware, don't use the reference in the range (i.e chat) as it's a shallow copy - state.AllChats[chat.ID] = &chats[i] + state.AllChats[chat.ID] = chats[i] - state.ModifiedChats[chat.ID] = true + state.Response.AddChat(chat) chatIDs = append(chatIDs, chat.ID) // Update name, currently is the only field is mutable } else if oldChat.Name != chat.Name { state.AllChats[chat.ID].Name = chat.Name - state.ModifiedChats[chat.ID] = true + state.Response.AddChat(chat) } } @@ -385,17 +390,37 @@ func (m *MessageHandler) HandleCommunityInvitation(state *ReceivedMessageState, return errors.New("invitation not for us") } - community, err := m.communitiesManager.HandleCommunityInvitation(signer, &invitation, rawPayload) + communityResponse, err := m.communitiesManager.HandleCommunityInvitation(signer, &invitation, rawPayload) if err != nil { return err } - state.AllCommunities[community.IDString()] = community + + community := communityResponse.Community + + state.Response.AddCommunity(community) + state.Response.CommunityChanges = append(state.Response.CommunityChanges, communityResponse.Changes) return nil } -// HandleWrappedCommunityDescriptionMessage handles a wrapped community description -func (m *MessageHandler) HandleWrappedCommunityDescriptionMessage(payload []byte) (*communities.Community, error) { +// HandleCommunityRequestToJoin handles an community request to join +func (m *MessageHandler) HandleCommunityRequestToJoin(state *ReceivedMessageState, signer *ecdsa.PublicKey, requestToJoinProto protobuf.CommunityRequestToJoin) error { + if requestToJoinProto.CommunityId == nil { + return errors.New("invalid community id") + } + + requestToJoin, err := m.communitiesManager.HandleCommunityRequestToJoin(signer, &requestToJoinProto) + if err != nil { + return err + } + + state.Response.RequestsToJoinCommunity = append(state.Response.RequestsToJoinCommunity, requestToJoin) + + return nil +} + +// handleWrappedCommunityDescriptionMessage handles a wrapped community description +func (m *MessageHandler) handleWrappedCommunityDescriptionMessage(payload []byte) (*communities.CommunityResponse, error) { return m.communitiesManager.HandleWrappedCommunityDescriptionMessage(payload) } @@ -466,26 +491,35 @@ func (m *MessageHandler) HandleChatMessage(state *ReceivedMessageState) error { // Set chat active chat.Active = true // Set in the modified maps chat - state.ModifiedChats[chat.ID] = true + state.Response.AddChat(chat) state.AllChats[chat.ID] = chat contact := state.CurrentMessageState.Contact - if hasENSNameChanged(contact, receivedMessage.EnsName, receivedMessage.Clock) { - contact.ResetENSVerification(receivedMessage.Clock, receivedMessage.EnsName) - state.ModifiedContacts[contact.ID] = true - state.AllContacts[contact.ID] = contact + if receivedMessage.EnsName != "" { + oldRecord, err := m.ensVerifier.Add(contact.ID, receivedMessage.EnsName, receivedMessage.Clock) + if err != nil { + m.logger.Warn("failed to verify ENS name", zap.Error(err)) + } else if oldRecord == nil { + // If oldRecord is nil, a new verification process will take place + // so we reset the record + contact.ENSVerified = false + state.ModifiedContacts[contact.ID] = true + state.AllContacts[contact.ID] = contact + } } if receivedMessage.ContentType == protobuf.ChatMessage_COMMUNITY { m.logger.Debug("Handling community content type") - community, err := m.HandleWrappedCommunityDescriptionMessage(receivedMessage.GetCommunity()) + communityResponse, err := m.handleWrappedCommunityDescriptionMessage(receivedMessage.GetCommunity()) if err != nil { return err } + community := communityResponse.Community receivedMessage.CommunityID = community.IDString() - state.AllCommunities[community.IDString()] = community + state.Response.AddCommunity(community) + state.Response.CommunityChanges = append(state.Response.CommunityChanges, communityResponse.Changes) } // Add to response state.Response.Messages = append(state.Response.Messages, receivedMessage) @@ -729,8 +763,7 @@ func (m *MessageHandler) matchChatEntity(chatEntity common.ChatEntity, chats map return nil, errors.Wrap(err, "failed to decode pubkey") } - newChat := CreateOneToOneChat(chatID[:8], pubKey, timesource) - chat = &newChat + chat = CreateOneToOneChat(chatID[:8], pubKey, timesource) } return chat, nil case chatEntity.GetMessageType() == protobuf.MessageType_ONE_TO_ONE: @@ -740,8 +773,7 @@ func (m *MessageHandler) matchChatEntity(chatEntity common.ChatEntity, chats map chat := chats[chatID] if chat == nil { // TODO: this should be a three-word name used in the mobile client - newChat := CreateOneToOneChat(chatID[:8], chatEntity.GetSigPubKey(), timesource) - chat = &newChat + chat = CreateOneToOneChat(chatID[:8], chatEntity.GetSigPubKey(), timesource) } return chat, nil case chatEntity.GetMessageType() == protobuf.MessageType_COMMUNITY_CHAT: @@ -863,7 +895,7 @@ func (m *MessageHandler) HandleEmojiReaction(state *ReceivedMessageState, pbEmoj chat.LastClockValue = pbEmojiR.Clock } - state.ModifiedChats[chat.ID] = true + state.Response.AddChat(chat) state.AllChats[chat.ID] = chat // save emoji reaction diff --git a/protocol/messenger.go b/protocol/messenger.go index 141addd42..2fa5a5aaa 100644 --- a/protocol/messenger.go +++ b/protocol/messenger.go @@ -23,7 +23,6 @@ import ( "github.com/status-im/status-go/eth-node/crypto" "github.com/status-im/status-go/eth-node/types" - enstypes "github.com/status-im/status-go/eth-node/types/ens" userimage "github.com/status-im/status-go/images" "github.com/status-im/status-go/multiaccounts" "github.com/status-im/status-go/protocol/audio" @@ -32,6 +31,7 @@ import ( "github.com/status-im/status-go/protocol/encryption" "github.com/status-im/status-go/protocol/encryption/multidevice" "github.com/status-im/status-go/protocol/encryption/sharedsecret" + "github.com/status-im/status-go/protocol/ens" "github.com/status-im/status-go/protocol/identity/alias" "github.com/status-im/status-go/protocol/identity/identicon" "github.com/status-im/status-go/protocol/images" @@ -81,6 +81,7 @@ type Messenger struct { encryptor *encryption.Protocol processor *common.MessageProcessor handler *MessageHandler + ensVerifier *ens.Verifier pushNotificationClient *pushnotificationclient.Client pushNotificationServer *pushnotificationserver.Server communitiesManager *communities.Manager @@ -271,12 +272,13 @@ func NewMessenger( pushNotificationClient := pushnotificationclient.New(pushNotificationClientPersistence, pushNotificationClientConfig, processor, &sqlitePersistence{db: database}) - communitiesManager, err := communities.NewManager(database, logger) + ensVerifier := ens.New(node, logger, transp, database, c.verifyENSURL, c.verifyENSContractAddress) + + communitiesManager, err := communities.NewManager(&identity.PublicKey, database, logger, ensVerifier) if err != nil { return nil, err } - - handler := newMessageHandler(identity, logger, &sqlitePersistence{db: database}, communitiesManager, transp) + handler := newMessageHandler(identity, logger, &sqlitePersistence{db: database}, communitiesManager, transp, ensVerifier) messenger = &Messenger{ config: &c, @@ -290,6 +292,7 @@ func NewMessenger( pushNotificationClient: pushNotificationClient, pushNotificationServer: pushNotificationServer, communitiesManager: communitiesManager, + ensVerifier: ensVerifier, featureFlags: c.featureFlags, systemMessagesTranslations: c.systemMessagesTranslations, allChats: make(map[string]*Chat), @@ -304,6 +307,7 @@ func NewMessenger( account: c.account, quit: make(chan struct{}), shutdownTasks: []func() error{ + ensVerifier.Stop, pushNotificationClient.Stop, communitiesManager.Stop, encryptionProtocol.Stop, @@ -413,6 +417,17 @@ func (m *Messenger) Start() (*MessengerResponse, error) { } } + ensSubscription := m.ensVerifier.Subscribe() + + // Subscrbe + if err := m.ensVerifier.Start(); err != nil { + return nil, err + } + + if err := m.communitiesManager.Start(); err != nil { + return nil, err + } + // set shared secret handles m.processor.SetHandleSharedSecrets(m.handleSharedSecrets) @@ -430,6 +445,7 @@ func (m *Messenger) Start() (*MessengerResponse, error) { m.handleEncryptionLayerSubscriptions(subscriptions) m.handleCommunitiesSubscription(m.communitiesManager.Subscribe()) m.handleConnectionChange(m.online()) + m.handleENSVerificationSubscription(ensSubscription) m.watchConnectionChange() m.watchExpiredEmojis() m.watchIdentityImageChanges() @@ -498,6 +514,7 @@ func (m *Messenger) handleConnectionChange(online bool) { m.pushNotificationClient.Offline() } } + m.ensVerifier.SetOnline(online) } func (m *Messenger) online() bool { @@ -788,106 +805,56 @@ func (m *Messenger) handleEncryptionLayerSubscriptions(subscriptions *encryption }() } -func (m *Messenger) publishOrg(org *communities.Community) error { - m.logger.Debug("publishing org", zap.String("org-id", org.IDString()), zap.Any("org", org)) - payload, err := org.MarshaledDescription() - if err != nil { - return err +func (m *Messenger) handleENSVerified(records []*ens.VerificationRecord) { + m.mutex.Lock() + defer m.mutex.Unlock() + + var contacts []*Contact + for _, record := range records { + m.logger.Info("handling record", zap.Any("record", record)) + contact, ok := m.allContacts[record.PublicKey] + if !ok { + m.logger.Info("contact not found") + continue + } + + contact.ENSVerified = record.Verified + contact.Name = record.Name + contacts = append(contacts, contact) } - rawMessage := common.RawMessage{ - Payload: payload, - Sender: org.PrivateKey(), - // we don't want to wrap in an encryption layer message - SkipEncryption: true, - MessageType: protobuf.ApplicationMetadataMessage_COMMUNITY_DESCRIPTION, + m.logger.Info("handled records", zap.Any("contacts", contacts)) + if len(contacts) != 0 { + if err := m.persistence.SaveContacts(contacts); err != nil { + m.logger.Error("failed to save contacts", zap.Error(err)) + return + } } - _, err = m.processor.SendPublic(context.Background(), org.IDString(), rawMessage) - return err + + m.logger.Info("calling on contacts") + if m.config.onContactENSVerified != nil { + m.logger.Info("called on contacts") + response := &MessengerResponse{Contacts: contacts} + m.config.onContactENSVerified(response) + } + } -func (m *Messenger) publishOrgInvitation(org *communities.Community, invitation *protobuf.CommunityInvitation) error { - m.logger.Debug("publishing org invitation", zap.String("org-id", org.IDString()), zap.Any("org", org)) - pk, err := crypto.DecompressPubkey(invitation.PublicKey) - if err != nil { - return err - } - - payload, err := proto.Marshal(invitation) - if err != nil { - return err - } - - rawMessage := common.RawMessage{ - Payload: payload, - Sender: org.PrivateKey(), - // we don't want to wrap in an encryption layer message - SkipEncryption: true, - MessageType: protobuf.ApplicationMetadataMessage_COMMUNITY_INVITATION, - } - _, err = m.processor.SendPrivate(context.Background(), pk, &rawMessage) - return err -} - -// handleCommunitiesSubscription handles events from communities -func (m *Messenger) handleCommunitiesSubscription(c chan *communities.Subscription) { - - var lastPublished int64 - // We check every 5 minutes if we need to publish - ticker := time.NewTicker(5 * time.Minute) - +func (m *Messenger) handleENSVerificationSubscription(c chan []*ens.VerificationRecord) { go func() { for { select { - case sub, more := <-c: + case records, more := <-c: if !more { + m.logger.Info("No more records, quitting") return } - if sub.Community != nil { - err := m.publishOrg(sub.Community) - if err != nil { - m.logger.Warn("failed to publish org", zap.Error(err)) - } + if len(records) != 0 { + m.logger.Info("handling records", zap.Any("records", records)) + m.handleENSVerified(records) } - - if sub.Invitation != nil { - err := m.publishOrgInvitation(sub.Community, sub.Invitation) - if err != nil { - m.logger.Warn("failed to publish org invitation", zap.Error(err)) - } - } - - m.logger.Debug("published org") - case <-ticker.C: - // If we are not online, we don't even try - if !m.online() { - continue - } - - // If not enough time has passed since last advertisement, we skip this - if time.Now().Unix()-lastPublished < communityAdvertiseIntervalSecond { - continue - } - - orgs, err := m.communitiesManager.Created() - if err != nil { - m.logger.Warn("failed to retrieve orgs", zap.Error(err)) - } - - for idx := range orgs { - org := orgs[idx] - err := m.publishOrg(org) - if err != nil { - m.logger.Warn("failed to publish org", zap.Error(err)) - } - } - - // set lastPublished - lastPublished = time.Now().Unix() - case <-m.quit: return - } } }() @@ -994,15 +961,31 @@ func (m *Messenger) Init() error { publicKeys []*ecdsa.PublicKey ) - communities, err := m.communitiesManager.Joined() + joinedCommunities, err := m.communitiesManager.Joined() if err != nil { return err } - for _, org := range communities { + for _, org := range joinedCommunities { // the org advertise on the public topic derived by the pk publicChatIDs = append(publicChatIDs, org.IDString()) } + // Init filters for the communities we are an admin of + var adminCommunitiesPks []*ecdsa.PrivateKey + adminCommunities, err := m.communitiesManager.Created() + if err != nil { + return err + } + + for _, c := range adminCommunities { + adminCommunitiesPks = append(adminCommunitiesPks, c.PrivateKey()) + } + + _, err = m.transport.InitCommunityFilters(adminCommunitiesPks) + if err != nil { + return err + } + // Get chat IDs and public keys from the existing chats. // TODO: Get only active chats by the query. chats, err := m.persistence.Chats() @@ -1182,28 +1165,6 @@ func (m *Messenger) Mailservers() ([]string, error) { return nil, ErrNotImplemented } -func (m *Messenger) Join(chat Chat) error { - switch chat.ChatType { - case ChatTypeOneToOne: - pk, err := chat.PublicKey() - if err != nil { - return err - } - - return m.transport.JoinPrivate(pk) - case ChatTypePrivateGroupChat: - members, err := chat.MembersAsPublicKeys() - if err != nil { - return err - } - return m.transport.JoinGroup(members) - case ChatTypePublic, ChatTypeProfile, ChatTypeTimeline: - return m.transport.JoinPublic(chat.ID) - default: - return errors.New("chat is neither public nor private") - } -} - // This is not accurate, it should not leave transport on removal of chat/group // only once there is no more: Group chat with that member, one-to-one chat, contact added by us func (m *Messenger) Leave(chat Chat) error { @@ -1288,7 +1249,7 @@ func (m *Messenger) CreateGroupChatWithMembers(ctx context.Context, name string, chat.updateChatFromGroupMembershipChanges(contactIDFromPublicKey(&m.identity.PublicKey), group) - response.Chats = []*Chat{&chat} + response.AddChat(&chat) response.Messages = buildSystemMessages(chat.MembershipUpdates, m.systemMessagesTranslations) err = m.persistence.SaveMessages(response.Messages) if err != nil { @@ -1310,7 +1271,7 @@ func (m *Messenger) CreateGroupChatFromInvitation(name string, chatID string, ad chat.Name = name chat.InvitationAdmin = adminPK - response.Chats = []*Chat{&chat} + response.AddChat(&chat) return &response, m.saveChat(&chat) } @@ -1369,7 +1330,7 @@ func (m *Messenger) RemoveMemberFromGroupChat(ctx context.Context, chatID string chat.updateChatFromGroupMembershipChanges(contactIDFromPublicKey(&m.identity.PublicKey), group) - response.Chats = []*Chat{chat} + response.AddChat(chat) response.Messages = buildSystemMessages(chat.MembershipUpdates, m.systemMessagesTranslations) err = m.persistence.SaveMessages(response.Messages) if err != nil { @@ -1458,7 +1419,7 @@ func (m *Messenger) AddMembersToGroupChat(ctx context.Context, chatID string, me chat.updateChatFromGroupMembershipChanges(contactIDFromPublicKey(&m.identity.PublicKey), group) - response.Chats = []*Chat{chat} + response.AddChat(chat) response.Messages = buildSystemMessages([]v1protocol.MembershipUpdateEvent{event}, m.systemMessagesTranslations) err = m.persistence.SaveMessages(response.Messages) if err != nil { @@ -1523,7 +1484,7 @@ func (m *Messenger) ChangeGroupChatName(ctx context.Context, chatID string, name chat.updateChatFromGroupMembershipChanges(contactIDFromPublicKey(&m.identity.PublicKey), group) var response MessengerResponse - response.Chats = []*Chat{chat} + response.AddChat(chat) response.Messages = buildSystemMessages([]v1protocol.MembershipUpdateEvent{event}, m.systemMessagesTranslations) err = m.persistence.SaveMessages(response.Messages) if err != nil { @@ -1732,7 +1693,7 @@ func (m *Messenger) AddAdminsToGroupChat(ctx context.Context, chatID string, mem chat.updateChatFromGroupMembershipChanges(contactIDFromPublicKey(&m.identity.PublicKey), group) - response.Chats = []*Chat{chat} + response.AddChat(chat) response.Messages = buildSystemMessages([]v1protocol.MembershipUpdateEvent{event}, m.systemMessagesTranslations) err = m.persistence.SaveMessages(response.Messages) if err != nil { @@ -1753,7 +1714,7 @@ func (m *Messenger) ConfirmJoiningGroup(ctx context.Context, chatID string) (*Me return nil, ErrChatNotFound } - err := m.Join(*chat) + _, err := m.Join(chat) if err != nil { return nil, err } @@ -1798,7 +1759,7 @@ func (m *Messenger) ConfirmJoiningGroup(ctx context.Context, chatID string) (*Me chat.updateChatFromGroupMembershipChanges(contactIDFromPublicKey(&m.identity.PublicKey), group) - response.Chats = []*Chat{chat} + response.AddChat(chat) response.Messages = buildSystemMessages([]v1protocol.MembershipUpdateEvent{event}, m.systemMessagesTranslations) err = m.persistence.SaveMessages(response.Messages) if err != nil { @@ -1869,7 +1830,7 @@ func (m *Messenger) LeaveGroupChat(ctx context.Context, chatID string, remove bo chat.Active = false } - response.Chats = []*Chat{chat} + response.AddChat(chat) response.Messages = buildSystemMessages([]v1protocol.MembershipUpdateEvent{event}, m.systemMessagesTranslations) err = m.persistence.SaveMessages(response.Messages) if err != nil { @@ -1879,307 +1840,6 @@ func (m *Messenger) LeaveGroupChat(ctx context.Context, chatID string, remove bo return &response, m.saveChat(chat) } -func (m *Messenger) saveChat(chat *Chat) error { - previousChat, ok := m.allChats[chat.ID] - if chat.OneToOne() { - name, identicon, err := generateAliasAndIdenticon(chat.ID) - if err != nil { - return err - } - - chat.Alias = name - chat.Identicon = identicon - } - // Sync chat if it's a new active public chat - if !ok && chat.Active && chat.Public() { - - if err := m.syncPublicChat(context.Background(), chat); err != nil { - return err - } - } - - // We check if it's a new chat, or chat.Active has changed - // we check here, but we only re-register once the chat has been - // saved an added - shouldRegisterForPushNotifications := chat.Public() && (!ok && chat.Active) || (ok && chat.Active != previousChat.Active) - - err := m.persistence.SaveChat(*chat) - if err != nil { - return err - } - m.allChats[chat.ID] = chat - - if shouldRegisterForPushNotifications { - // Re-register for push notifications, as we want to receive mentions - if err := m.reregisterForPushNotifications(); err != nil { - return err - } - - } - - return nil -} - -func (m *Messenger) saveChats(chats []*Chat) error { - err := m.persistence.SaveChats(chats) - if err != nil { - return err - } - for _, chat := range chats { - m.allChats[chat.ID] = chat - } - - return nil - -} - -func (m *Messenger) SaveChat(chat *Chat) error { - m.mutex.Lock() - defer m.mutex.Unlock() - return m.saveChat(chat) -} - -func (m *Messenger) Chats() []*Chat { - m.mutex.Lock() - defer m.mutex.Unlock() - - var chats []*Chat - - for _, c := range m.allChats { - chats = append(chats, c) - } - - return chats -} - -func (m *Messenger) Communities() ([]*communities.Community, error) { - return m.communitiesManager.All() -} - -func (m *Messenger) JoinedCommunities() ([]*communities.Community, error) { - return m.communitiesManager.Joined() -} - -func (m *Messenger) JoinCommunity(communityID string) (*MessengerResponse, error) { - m.mutex.Lock() - defer m.mutex.Unlock() - - response := &MessengerResponse{} - - org, err := m.communitiesManager.JoinCommunity(communityID) - if err != nil { - return nil, err - } - - chatIDs := []string{org.IDString()} - - chats := CreateCommunityChats(org, m.getTimesource()) - - // Beware don't use `chat` as a reference - for i, chat := range chats { - chatIDs = append(chatIDs, chat.ID) - response.Chats = append(response.Chats, &chats[i]) - } - - // Load transport filters - filters, err := m.transport.InitPublicFilters(chatIDs) - if err != nil { - return nil, err - } - - response.Filters = filters - response.Communities = []*communities.Community{org} - - return response, m.saveChats(response.Chats) -} - -func (m *Messenger) LeaveCommunity(communityID string) (*MessengerResponse, error) { - response := &MessengerResponse{} - - org, err := m.communitiesManager.LeaveCommunity(communityID) - if err != nil { - return nil, err - } - - // Make chat inactive - for chatID := range org.Chats() { - orgChatID := communityID + chatID - err := m.DeleteChat(orgChatID) - if err != nil { - return nil, err - } - response.RemovedChats = append(response.RemovedChats, orgChatID) - - filter, err := m.transport.RemoveFilterByChatID(orgChatID) - if err != nil { - return nil, err - } - - if filter != nil { - response.RemovedFilters = append(response.RemovedFilters, filter) - } - } - - filter, err := m.transport.RemoveFilterByChatID(communityID) - if err != nil { - return nil, err - } - - if filter != nil { - response.RemovedFilters = append(response.RemovedFilters, filter) - } - - response.Communities = []*communities.Community{org} - return response, nil -} - -func (m *Messenger) CreateCommunityChat(orgID string, c *protobuf.CommunityChat) (*MessengerResponse, error) { - org, changes, err := m.communitiesManager.CreateChat(orgID, c) - if err != nil { - return nil, err - } - var chats []*Chat - var chatIDs []string - for chatID, chat := range changes.ChatsAdded { - c := CreateCommunityChat(org.IDString(), chatID, chat, m.getTimesource()) - chats = append(chats, &c) - chatIDs = append(chatIDs, c.ID) - } - - // Load filters - filters, err := m.transport.InitPublicFilters(chatIDs) - if err != nil { - return nil, err - } - - return &MessengerResponse{ - Communities: []*communities.Community{org}, - Chats: chats, - Filters: filters, - CommunityChanges: []*communities.CommunityChanges{changes}, - }, m.saveChats(chats) -} - -func (m *Messenger) CreateCommunity(description *protobuf.CommunityDescription) (*MessengerResponse, error) { - org, err := m.communitiesManager.CreateCommunity(description) - if err != nil { - return nil, err - } - - return &MessengerResponse{ - Communities: []*communities.Community{org}, - }, nil -} - -func (m *Messenger) ExportCommunity(id string) (*ecdsa.PrivateKey, error) { - return m.communitiesManager.ExportCommunity(id) -} - -func (m *Messenger) ImportCommunity(key *ecdsa.PrivateKey) (*MessengerResponse, error) { - org, err := m.communitiesManager.ImportCommunity(key) - if err != nil { - return nil, err - } - - // Load filters - filters, err := m.transport.InitPublicFilters([]string{org.IDString()}) - if err != nil { - return nil, err - } - - return &MessengerResponse{ - Filters: filters, - }, nil -} - -func (m *Messenger) InviteUserToCommunity(orgID, pkString string) (*MessengerResponse, error) { - publicKey, err := common.HexToPubkey(pkString) - if err != nil { - return nil, err - } - - org, err := m.communitiesManager.InviteUserToCommunity(orgID, publicKey) - if err != nil { - return nil, err - } - - return &MessengerResponse{ - Communities: []*communities.Community{org}, - }, nil -} - -func (m *Messenger) RemoveUserFromCommunity(orgID, pkString string) (*MessengerResponse, error) { - publicKey, err := common.HexToPubkey(pkString) - if err != nil { - return nil, err - } - - org, err := m.communitiesManager.RemoveUserFromCommunity(orgID, publicKey) - if err != nil { - return nil, err - } - - return &MessengerResponse{ - Communities: []*communities.Community{org}, - }, nil -} - -func (m *Messenger) DeleteChat(chatID string) error { - m.mutex.Lock() - defer m.mutex.Unlock() - - err := m.persistence.DeleteChat(chatID) - if err != nil { - return err - } - chat, ok := m.allChats[chatID] - - if ok && chat.Active && chat.Public() { - delete(m.allChats, chatID) - return m.reregisterForPushNotifications() - } - - return nil -} - -func (m *Messenger) DeactivateChat(chatID string) (*MessengerResponse, error) { - m.mutex.Lock() - defer m.mutex.Unlock() - return m.deactivateChat(chatID) -} - -func (m *Messenger) deactivateChat(chatID string) (*MessengerResponse, error) { - var response MessengerResponse - chat, ok := m.allChats[chatID] - if !ok { - return nil, ErrChatNotFound - } - - clock, _ := chat.NextClockAndTimestamp(m.getTimesource()) - - err := m.persistence.DeactivateChat(chat, clock) - - if err != nil { - return nil, err - } - - // We re-register as our options have changed and we don't want to - // receive PN from mentions in this chat anymore - if chat.Public() { - err := m.reregisterForPushNotifications() - if err != nil { - return nil, err - } - } - - m.allChats[chatID] = chat - - response.Chats = []*Chat{chat} - // TODO: Remove filters - - return &response, nil -} - func (m *Messenger) reregisterForPushNotifications() error { m.logger.Info("contact state changed, re-registering for push notification") if m.pushNotificationClient == nil { @@ -2533,7 +2193,7 @@ func (m *Messenger) sendChatMessage(ctx context.Context, message *common.Message return nil, err } - response.Chats = []*Chat{chat} + response.AddChat(chat) return &response, m.saveChat(chat) } @@ -2617,7 +2277,7 @@ func (m *Messenger) SendPairInstallation(ctx context.Context) (*MessengerRespons return nil, err } - response.Chats = []*Chat{chat} + response.AddChat(chat) chat.LastClockValue = clock err = m.saveChat(chat) @@ -2740,8 +2400,7 @@ type ReceivedMessageState struct { CurrentMessageState *CurrentMessageState // AllChats in memory AllChats map[string]*Chat - // List of chats modified - ModifiedChats map[string]bool + // All contacts in memory AllContacts map[string]*Contact // List of contacts modified @@ -2750,8 +2409,6 @@ type ReceivedMessageState struct { AllInstallations map[string]*multidevice.Installation // List of communities modified ModifiedInstallations map[string]bool - // List of communities - AllCommunities map[string]*communities.Community // List of filters AllFilters map[string]*transport.Filter // Map of existing messages @@ -2829,14 +2486,12 @@ func (m *Messenger) handleRetrievedMessages(chatWithMessages map[transport.Filte defer m.mutex.Unlock() messageState := &ReceivedMessageState{ AllChats: m.allChats, - ModifiedChats: make(map[string]bool), AllContacts: m.allContacts, ModifiedContacts: make(map[string]bool), AllInstallations: m.allInstallations, ModifiedInstallations: m.modifiedInstallations, ExistingMessagesMap: make(map[string]bool), EmojiReactions: make(map[string]*EmojiReaction), - AllCommunities: make(map[string]*communities.Community), AllFilters: make(map[string]*transport.Filter), GroupChatInvitations: make(map[string]*GroupChatInvitation), Response: &MessengerResponse{}, @@ -3180,10 +2835,19 @@ func (m *Messenger) handleRetrievedMessages(chatWithMessages map[transport.Filte invitation := msg.ParsedMessage.Interface().(protobuf.CommunityInvitation) err = m.handler.HandleCommunityInvitation(messageState, publicKey, invitation, invitation.CommunityDescription) if err != nil { - logger.Warn("failed to handle CommunityDescription", zap.Error(err)) + logger.Warn("failed to handle CommunityInvitation", zap.Error(err)) allMessagesProcessed = false continue } + case protobuf.CommunityRequestToJoin: + logger.Debug("Handling CommunityRequestToJoin") + request := msg.ParsedMessage.Interface().(protobuf.CommunityRequestToJoin) + err = m.handler.HandleCommunityRequestToJoin(messageState, publicKey, request) + if err != nil { + logger.Warn("failed to handle CommunityRequestToJoin", zap.Error(err)) + continue + } + default: // Check if is an encrypted PushNotificationRegistration if msg.Type == protobuf.ApplicationMetadataMessage_PUSH_NOTIFICATION_REGISTRATION { @@ -3208,6 +2872,39 @@ func (m *Messenger) handleRetrievedMessages(chatWithMessages map[transport.Filte } } + // Process any community changes + for _, changes := range messageState.Response.CommunityChanges { + if changes.ShouldMemberJoin { + response, err := m.joinCommunity(changes.Community.ID()) + if err != nil { + logger.Error("cannot join community", zap.Error(err)) + continue + } + + if err := messageState.Response.Merge(response); err != nil { + logger.Error("cannot merge join community response", zap.Error(err)) + continue + } + + } else if changes.ShouldMemberLeave { + response, err := m.leaveCommunity(changes.Community.ID()) + if err != nil { + logger.Error("cannot join community", zap.Error(err)) + continue + } + + if err := messageState.Response.Merge(response); err != nil { + logger.Error("cannot merge join community response", zap.Error(err)) + continue + + } + + } + } + + // Clean up as not used by clients currently + messageState.Response.CommunityChanges = nil + if allMessagesProcessed { processedMessages = append(processedMessages, types.EncodeHex(shhMessage.Hash)) } @@ -3234,15 +2931,12 @@ func (m *Messenger) handleRetrievedMessages(chatWithMessages map[transport.Filte } } - for _, community := range messageState.AllCommunities { - messageState.Response.Communities = append(messageState.Response.Communities, community) - } - for _, filter := range messageState.AllFilters { messageState.Response.Filters = append(messageState.Response.Filters, filter) } - for id := range messageState.ModifiedChats { + // Hydrate chat alias and identicon + for id := range messageState.Response.chats { chat := messageState.AllChats[id] if chat.OneToOne() { contact, ok := m.allContacts[chat.ID] @@ -3252,7 +2946,7 @@ func (m *Messenger) handleRetrievedMessages(chatWithMessages map[transport.Filte } } - messageState.Response.Chats = append(messageState.Response.Chats, chat) + messageState.Response.AddChat(chat) } for id := range messageState.ModifiedInstallations { @@ -3267,12 +2961,13 @@ func (m *Messenger) handleRetrievedMessages(chatWithMessages map[transport.Filte } var err error - if len(messageState.Response.Chats) > 0 { - err = m.saveChats(messageState.Response.Chats) + if len(messageState.Response.chats) > 0 { + err = m.saveChats(messageState.Response.Chats()) if err != nil { return nil, err } } + if len(messageState.Response.Messages) > 0 { err = m.SaveMessages(messageState.Response.Messages) if err != nil { @@ -3448,7 +3143,8 @@ func (m *Messenger) clearHistory(id string) (*MessengerResponse, error) { m.allChats[id] = chat - response := &MessengerResponse{Chats: []*Chat{chat}} + response := &MessengerResponse{} + response.AddChat(chat) return response, nil } @@ -3539,72 +3235,6 @@ func Identicon(id string) (string, error) { return identicon.GenerateBase64(id) } -// VerifyENSNames verifies that a registered ENS name matches the expected public key -func (m *Messenger) VerifyENSNames(ctx context.Context, rpcEndpoint, contractAddress string) (*MessengerResponse, error) { - m.mutex.Lock() - defer m.mutex.Unlock() - - m.logger.Debug("verifying ENS Names", zap.String("endpoint", rpcEndpoint)) - verifier := m.node.NewENSVerifier(m.logger) - - var response MessengerResponse - - var ensDetails []enstypes.ENSDetails - - // Now in seconds - now := m.getTimesource().GetCurrentTime() / 1000 - for _, contact := range m.allContacts { - if shouldENSBeVerified(contact, now) { - ensDetails = append(ensDetails, enstypes.ENSDetails{ - PublicKeyString: contact.ID[2:], - Name: contact.Name, - }) - } - } - - ensResponse, err := verifier.CheckBatch(ensDetails, rpcEndpoint, contractAddress) - if err != nil { - return nil, err - } - - for _, details := range ensResponse { - contact, ok := m.allContacts["0x"+details.PublicKeyString] - if !ok { - return nil, errors.New("contact must be existing") - } - - m.logger.Debug("verifying ENS Name", zap.Any("details", details), zap.Any("contact", contact)) - - contact.ENSVerifiedAt = uint64(details.VerifiedAt) - - if details.Error == nil { - contact.ENSVerified = details.Verified - // Increment count if not verified, even if no error - if !details.Verified { - contact.ENSVerificationRetries++ - } - m.allContacts[contact.ID] = contact - } else { - m.logger.Warn("Failed to resolve ens name", - zap.String("name", details.Name), - zap.String("publicKey", details.PublicKeyString), - zap.Error(details.Error), - ) - contact.ENSVerificationRetries++ - } - response.Contacts = append(response.Contacts, contact) - } - - if len(response.Contacts) != 0 { - err = m.persistence.SaveContacts(response.Contacts) - if err != nil { - return nil, err - } - } - - return &response, nil -} - // GenerateAlias name returns the generated name given a public key hex encoded prefixed with 0x func GenerateAlias(id string) (string, error) { return alias.GenerateFromPublicKeyString(id) @@ -3683,7 +3313,7 @@ func (m *Messenger) RequestTransaction(ctx context.Context, chatID, value, contr return nil, err } - response.Chats = []*Chat{chat} + response.AddChat(chat) response.Messages = []*common.Message{message} return &response, m.saveChat(chat) } @@ -3760,7 +3390,7 @@ func (m *Messenger) RequestAddressForTransaction(ctx context.Context, chatID, fr return nil, err } - response.Chats = []*Chat{chat} + response.AddChat(chat) response.Messages = []*common.Message{message} return &response, m.saveChat(chat) } @@ -3856,7 +3486,7 @@ func (m *Messenger) AcceptRequestAddressForTransaction(ctx context.Context, mess return nil, err } - response.Chats = []*Chat{chat} + response.AddChat(chat) response.Messages = []*common.Message{message} return &response, m.saveChat(chat) } @@ -3939,7 +3569,7 @@ func (m *Messenger) DeclineRequestTransaction(ctx context.Context, messageID str return nil, err } - response.Chats = []*Chat{chat} + response.AddChat(chat) response.Messages = []*common.Message{message} return &response, m.saveChat(chat) } @@ -4022,7 +3652,7 @@ func (m *Messenger) DeclineRequestAddressForTransaction(ctx context.Context, mes return nil, err } - response.Chats = []*Chat{chat} + response.AddChat(chat) response.Messages = []*common.Message{message} return &response, m.saveChat(chat) } @@ -4122,7 +3752,7 @@ func (m *Messenger) AcceptRequestTransaction(ctx context.Context, transactionHas return nil, err } - response.Chats = []*Chat{chat} + response.AddChat(chat) response.Messages = []*common.Message{message} return &response, m.saveChat(chat) } @@ -4204,7 +3834,7 @@ func (m *Messenger) SendTransaction(ctx context.Context, chatID, value, contract return nil, err } - response.Chats = []*Chat{chat} + response.AddChat(chat) response.Messages = []*common.Message{message} return &response, m.saveChat(chat) } @@ -4216,8 +3846,6 @@ func (m *Messenger) ValidateTransactions(ctx context.Context, addresses []types. m.mutex.Lock() defer m.mutex.Unlock() - modifiedChats := make(map[string]bool) - logger := m.logger.With(zap.String("site", "ValidateTransactions")) logger.Debug("Validating transactions") txs, err := m.persistence.TransactionsToValidate() @@ -4303,7 +3931,7 @@ func (m *Messenger) ValidateTransactions(ctx context.Context, addresses []types. response.Messages = append(response.Messages, message) m.allChats[chat.ID] = chat - modifiedChats[chat.ID] = true + response.AddChat(chat) contact, err := m.getOrBuildContactFromMessage(message) if err != nil { @@ -4316,9 +3944,6 @@ func (m *Messenger) ValidateTransactions(ctx context.Context, addresses []types. }) } - for id := range modifiedChats { - response.Chats = append(response.Chats, m.allChats[id]) - } if len(response.Messages) > 0 { err = m.SaveMessages(response.Messages) @@ -4578,7 +4203,7 @@ func (m *Messenger) SendEmojiReaction(ctx context.Context, chatID, messageID str } response.EmojiReactions = []*EmojiReaction{emojiR} - response.Chats = []*Chat{chat} + response.AddChat(chat) err = m.persistence.SaveEmojiReaction(emojiR) if err != nil { @@ -4659,7 +4284,7 @@ func (m *Messenger) SendEmojiReactionRetraction(ctx context.Context, emojiReacti response := MessengerResponse{} emojiR.Retracted = true response.EmojiReactions = []*EmojiReaction{emojiR} - response.Chats = []*Chat{chat} + response.AddChat(chat) // Persist retraction state for emoji reaction err = m.persistence.SaveEmojiReaction(emojiR) diff --git a/protocol/messenger_chats.go b/protocol/messenger_chats.go new file mode 100644 index 000000000..610dd6b62 --- /dev/null +++ b/protocol/messenger_chats.go @@ -0,0 +1,216 @@ +package protocol + +import ( + "context" + "errors" + "strings" + + "github.com/status-im/status-go/protocol/common" + "github.com/status-im/status-go/protocol/requests" + "github.com/status-im/status-go/protocol/transport" +) + +func (m *Messenger) Chats() []*Chat { + m.mutex.Lock() + defer m.mutex.Unlock() + + var chats []*Chat + + for _, c := range m.allChats { + chats = append(chats, c) + } + + return chats +} + +func (m *Messenger) CreateOneToOneChat(request *requests.CreateOneToOneChat) (*MessengerResponse, error) { + m.mutex.Lock() + defer m.mutex.Unlock() + + if err := request.Validate(); err != nil { + return nil, err + } + + chatID := request.ID.String() + pk, err := common.HexToPubkey(chatID) + if err != nil { + return nil, err + } + + chat, ok := m.allChats[chatID] + if !ok { + chat = CreateOneToOneChat(chatID, pk, m.getTimesource()) + } + chat.Active = true + + filters, err := m.Join(chat) + if err != nil { + return nil, err + } + + err = m.saveChat(chat) + if err != nil { + return nil, err + } + + m.allChats[chatID] = chat + + response := &MessengerResponse{ + Filters: filters, + } + response.AddChat(chat) + + return response, nil + +} + +func (m *Messenger) DeleteChat(chatID string) error { + m.mutex.Lock() + defer m.mutex.Unlock() + + return m.deleteChat(chatID) +} + +func (m *Messenger) deleteChat(chatID string) error { + err := m.persistence.DeleteChat(chatID) + if err != nil { + return err + } + chat, ok := m.allChats[chatID] + + if ok && chat.Active && chat.Public() { + delete(m.allChats, chatID) + return m.reregisterForPushNotifications() + } + + return nil +} + +func (m *Messenger) SaveChat(chat *Chat) error { + m.mutex.Lock() + defer m.mutex.Unlock() + return m.saveChat(chat) +} + +func (m *Messenger) DeactivateChat(chatID string) (*MessengerResponse, error) { + m.mutex.Lock() + defer m.mutex.Unlock() + return m.deactivateChat(chatID) +} + +func (m *Messenger) deactivateChat(chatID string) (*MessengerResponse, error) { + var response MessengerResponse + chat, ok := m.allChats[chatID] + if !ok { + return nil, ErrChatNotFound + } + + clock, _ := chat.NextClockAndTimestamp(m.getTimesource()) + + err := m.persistence.DeactivateChat(chat, clock) + + if err != nil { + return nil, err + } + + // We re-register as our options have changed and we don't want to + // receive PN from mentions in this chat anymore + if chat.Public() { + err := m.reregisterForPushNotifications() + if err != nil { + return nil, err + } + } + + m.allChats[chatID] = chat + + response.AddChat(chat) + // TODO: Remove filters + + return &response, nil +} + +func (m *Messenger) saveChats(chats []*Chat) error { + err := m.persistence.SaveChats(chats) + if err != nil { + return err + } + for _, chat := range chats { + m.allChats[chat.ID] = chat + } + + return nil + +} + +func (m *Messenger) saveChat(chat *Chat) error { + previousChat, ok := m.allChats[chat.ID] + if chat.OneToOne() { + name, identicon, err := generateAliasAndIdenticon(chat.ID) + if err != nil { + return err + } + + chat.Alias = name + chat.Identicon = identicon + } + // Sync chat if it's a new active public chat, but not a timeline chat + if !ok && chat.Active && chat.Public() && !strings.HasPrefix(chat.ID, "@") { + + if err := m.syncPublicChat(context.Background(), chat); err != nil { + return err + } + } + + // We check if it's a new chat, or chat.Active has changed + // we check here, but we only re-register once the chat has been + // saved an added + shouldRegisterForPushNotifications := chat.Public() && (!ok && chat.Active) || (ok && chat.Active != previousChat.Active) + + err := m.persistence.SaveChat(*chat) + if err != nil { + return err + } + m.allChats[chat.ID] = chat + + if shouldRegisterForPushNotifications { + // Re-register for push notifications, as we want to receive mentions + if err := m.reregisterForPushNotifications(); err != nil { + return err + } + + } + + return nil +} + +func (m *Messenger) Join(chat *Chat) ([]*transport.Filter, error) { + switch chat.ChatType { + case ChatTypeOneToOne: + pk, err := chat.PublicKey() + if err != nil { + return nil, err + } + + f, err := m.transport.JoinPrivate(pk) + if err != nil { + return nil, err + } + + return []*transport.Filter{f}, nil + case ChatTypePrivateGroupChat: + members, err := chat.MembersAsPublicKeys() + if err != nil { + return nil, err + } + return m.transport.JoinGroup(members) + case ChatTypePublic, ChatTypeProfile, ChatTypeTimeline: + f, err := m.transport.JoinPublic(chat.ID) + if err != nil { + return nil, err + } + return []*transport.Filter{f}, nil + default: + return nil, errors.New("chat is neither public nor private") + } +} diff --git a/protocol/messenger_communities.go b/protocol/messenger_communities.go new file mode 100644 index 000000000..581a6e9e9 --- /dev/null +++ b/protocol/messenger_communities.go @@ -0,0 +1,465 @@ +package protocol + +import ( + "context" + "crypto/ecdsa" + "time" + + "github.com/golang/protobuf/proto" + "go.uber.org/zap" + + "github.com/status-im/status-go/eth-node/crypto" + "github.com/status-im/status-go/eth-node/types" + "github.com/status-im/status-go/protocol/common" + "github.com/status-im/status-go/protocol/communities" + "github.com/status-im/status-go/protocol/protobuf" + "github.com/status-im/status-go/protocol/requests" +) + +const communityInvitationText = "Upgrade to see a community invitation" + +func (m *Messenger) publishOrg(org *communities.Community) error { + m.logger.Debug("publishing org", zap.String("org-id", org.IDString()), zap.Any("org", org)) + payload, err := org.MarshaledDescription() + if err != nil { + return err + } + + rawMessage := common.RawMessage{ + Payload: payload, + Sender: org.PrivateKey(), + // we don't want to wrap in an encryption layer message + SkipEncryption: true, + MessageType: protobuf.ApplicationMetadataMessage_COMMUNITY_DESCRIPTION, + } + _, err = m.processor.SendPublic(context.Background(), org.IDString(), rawMessage) + return err +} + +func (m *Messenger) publishOrgInvitation(org *communities.Community, invitation *protobuf.CommunityInvitation) error { + m.logger.Debug("publishing org invitation", zap.String("org-id", org.IDString()), zap.Any("org", org)) + pk, err := crypto.DecompressPubkey(invitation.PublicKey) + if err != nil { + return err + } + + payload, err := proto.Marshal(invitation) + if err != nil { + return err + } + + rawMessage := common.RawMessage{ + Payload: payload, + Sender: org.PrivateKey(), + // we don't want to wrap in an encryption layer message + SkipEncryption: true, + MessageType: protobuf.ApplicationMetadataMessage_COMMUNITY_INVITATION, + } + _, err = m.processor.SendPrivate(context.Background(), pk, &rawMessage) + return err +} + +// handleCommunitiesSubscription handles events from communities +func (m *Messenger) handleCommunitiesSubscription(c chan *communities.Subscription) { + + var lastPublished int64 + // We check every 5 minutes if we need to publish + ticker := time.NewTicker(5 * time.Minute) + + go func() { + for { + select { + case sub, more := <-c: + if !more { + return + } + if sub.Community != nil { + err := m.publishOrg(sub.Community) + if err != nil { + m.logger.Warn("failed to publish org", zap.Error(err)) + } + } + + for _, invitation := range sub.Invitations { + err := m.publishOrgInvitation(sub.Community, invitation) + if err != nil { + m.logger.Warn("failed to publish org invitation", zap.Error(err)) + } + } + + m.logger.Debug("published org") + case <-ticker.C: + // If we are not online, we don't even try + if !m.online() { + continue + } + + // If not enough time has passed since last advertisement, we skip this + if time.Now().Unix()-lastPublished < communityAdvertiseIntervalSecond { + continue + } + + orgs, err := m.communitiesManager.Created() + if err != nil { + m.logger.Warn("failed to retrieve orgs", zap.Error(err)) + } + + for idx := range orgs { + org := orgs[idx] + err := m.publishOrg(org) + if err != nil { + m.logger.Warn("failed to publish org", zap.Error(err)) + } + } + + // set lastPublished + lastPublished = time.Now().Unix() + + case <-m.quit: + return + + } + } + }() +} + +func (m *Messenger) Communities() ([]*communities.Community, error) { + return m.communitiesManager.All() +} + +func (m *Messenger) JoinedCommunities() ([]*communities.Community, error) { + return m.communitiesManager.Joined() +} + +func (m *Messenger) JoinCommunity(communityID types.HexBytes) (*MessengerResponse, error) { + m.mutex.Lock() + defer m.mutex.Unlock() + + return m.joinCommunity(communityID) +} + +func (m *Messenger) joinCommunity(communityID types.HexBytes) (*MessengerResponse, error) { + response := &MessengerResponse{} + + community, err := m.communitiesManager.JoinCommunity(communityID) + if err != nil { + return nil, err + } + + chatIDs := []string{community.IDString()} + + chats := CreateCommunityChats(community, m.getTimesource()) + response.AddChats(chats) + + for _, chat := range response.Chats() { + chatIDs = append(chatIDs, chat.ID) + } + + // Load transport filters + filters, err := m.transport.InitPublicFilters(chatIDs) + if err != nil { + return nil, err + } + + response.Filters = filters + response.AddCommunity(community) + + return response, m.saveChats(chats) +} + +func (m *Messenger) RequestToJoinCommunity(request *requests.RequestToJoinCommunity) (*MessengerResponse, error) { + if err := request.Validate(); err != nil { + return nil, err + } + + community, requestToJoin, err := m.communitiesManager.RequestToJoin(&m.identity.PublicKey, request) + if err != nil { + return nil, err + } + + requestToJoinProto := &protobuf.CommunityRequestToJoin{ + Clock: requestToJoin.Clock, + EnsName: requestToJoin.ENSName, + CommunityId: community.ID(), + } + + payload, err := proto.Marshal(requestToJoinProto) + if err != nil { + return nil, err + } + + rawMessage := common.RawMessage{ + Payload: payload, + SkipEncryption: true, + MessageType: protobuf.ApplicationMetadataMessage_COMMUNITY_REQUEST_TO_JOIN, + } + _, err = m.processor.SendCommunityMessage(context.Background(), community.PublicKey(), rawMessage) + if err != nil { + return nil, err + } + + response := &MessengerResponse{RequestsToJoinCommunity: []*communities.RequestToJoin{requestToJoin}} + response.AddCommunity(community) + + return response, nil +} + +func (m *Messenger) AcceptRequestToJoinCommunity(request *requests.AcceptRequestToJoinCommunity) (*MessengerResponse, error) { + if err := request.Validate(); err != nil { + return nil, err + } + + community, err := m.communitiesManager.AcceptRequestToJoin(request) + + if err != nil { + return nil, err + } + + response := &MessengerResponse{} + response.AddCommunity(community) + return response, nil +} + +func (m *Messenger) DeclineRequestToJoinCommunity(request *requests.DeclineRequestToJoinCommunity) error { + if err := request.Validate(); err != nil { + return err + } + + return m.communitiesManager.DeclineRequestToJoin(request) +} + +func (m *Messenger) LeaveCommunity(communityID types.HexBytes) (*MessengerResponse, error) { + m.mutex.Lock() + defer m.mutex.Unlock() + + return m.leaveCommunity(communityID) +} + +func (m *Messenger) leaveCommunity(communityID types.HexBytes) (*MessengerResponse, error) { + response := &MessengerResponse{} + + community, err := m.communitiesManager.LeaveCommunity(communityID) + if err != nil { + return nil, err + } + + // Make chat inactive + for chatID := range community.Chats() { + communityChatID := communityID.String() + chatID + err := m.deleteChat(communityChatID) + if err != nil { + return nil, err + } + response.AddRemovedChat(communityChatID) + + filter, err := m.transport.RemoveFilterByChatID(communityChatID) + if err != nil { + return nil, err + } + + if filter != nil { + response.RemovedFilters = append(response.RemovedFilters, filter) + } + } + + filter, err := m.transport.RemoveFilterByChatID(communityID.String()) + if err != nil { + return nil, err + } + + if filter != nil { + response.RemovedFilters = append(response.RemovedFilters, filter) + } + + response.AddCommunity(community) + return response, nil +} + +func (m *Messenger) CreateCommunityChat(communityID types.HexBytes, c *protobuf.CommunityChat) (*MessengerResponse, error) { + var response MessengerResponse + community, changes, err := m.communitiesManager.CreateChat(communityID, c) + if err != nil { + return nil, err + } + response.AddCommunity(community) + response.CommunityChanges = []*communities.CommunityChanges{changes} + + var chats []*Chat + var chatIDs []string + for chatID, chat := range changes.ChatsAdded { + c := CreateCommunityChat(community.IDString(), chatID, chat, m.getTimesource()) + chats = append(chats, c) + chatIDs = append(chatIDs, c.ID) + response.AddChat(c) + } + + // Load filters + filters, err := m.transport.InitPublicFilters(chatIDs) + if err != nil { + return nil, err + } + response.Filters = filters + + return &response, m.saveChats(chats) +} + +func (m *Messenger) CreateCommunity(request *requests.CreateCommunity) (*MessengerResponse, error) { + if err := request.Validate(); err != nil { + return nil, err + } + + description, err := request.ToCommunityDescription() + if err != nil { + return nil, err + } + + description.Members = make(map[string]*protobuf.CommunityMember) + description.Members[common.PubkeyToHex(&m.identity.PublicKey)] = &protobuf.CommunityMember{} + + community, err := m.communitiesManager.CreateCommunity(description) + if err != nil { + return nil, err + } + + // Init the community filter so we can receive messages on the community + filters, err := m.transport.InitCommunityFilters([]*ecdsa.PrivateKey{community.PrivateKey()}) + if err != nil { + return nil, err + } + + response := &MessengerResponse{ + Filters: filters, + } + + response.AddCommunity(community) + + return response, nil +} + +func (m *Messenger) ExportCommunity(id types.HexBytes) (*ecdsa.PrivateKey, error) { + return m.communitiesManager.ExportCommunity(id) +} + +func (m *Messenger) ImportCommunity(key *ecdsa.PrivateKey) (*MessengerResponse, error) { + org, err := m.communitiesManager.ImportCommunity(key) + if err != nil { + return nil, err + } + + // Load filters + filters, err := m.transport.InitPublicFilters([]string{org.IDString()}) + if err != nil { + return nil, err + } + + return &MessengerResponse{ + Filters: filters, + }, nil +} + +func (m *Messenger) InviteUsersToCommunity(request *requests.InviteUsersToCommunity) (*MessengerResponse, error) { + if err := request.Validate(); err != nil { + return nil, err + } + + response := &MessengerResponse{} + var messages []*common.Message + + var publicKeys []*ecdsa.PublicKey + for _, pkBytes := range request.Users { + publicKey, err := common.HexToPubkey(pkBytes.String()) + if err != nil { + return nil, err + } + publicKeys = append(publicKeys, publicKey) + + message := &common.Message{} + message.ChatId = pkBytes.String() + message.CommunityID = request.CommunityID.String() + message.Text = communityInvitationText + messages = append(messages, message) + r, err := m.CreateOneToOneChat(&requests.CreateOneToOneChat{ID: pkBytes}) + if err != nil { + return nil, err + } + + if err := response.Merge(r); err != nil { + return nil, err + } + } + + community, err := m.communitiesManager.InviteUsersToCommunity(request.CommunityID, publicKeys) + if err != nil { + return nil, err + } + sendMessagesResponse, err := m.SendChatMessages(context.Background(), messages) + if err != nil { + return nil, err + } + + if err := response.Merge(sendMessagesResponse); err != nil { + return nil, err + } + + response.AddCommunity(community) + return response, nil +} + +func (m *Messenger) ShareCommunity(request *requests.ShareCommunity) (*MessengerResponse, error) { + if err := request.Validate(); err != nil { + return nil, err + } + response := &MessengerResponse{} + + var messages []*common.Message + for _, pk := range request.Users { + message := &common.Message{} + message.ChatId = pk.String() + message.CommunityID = request.CommunityID.String() + message.Text = communityInvitationText + messages = append(messages, message) + r, err := m.CreateOneToOneChat(&requests.CreateOneToOneChat{ID: pk}) + if err != nil { + return nil, err + } + + if err := response.Merge(r); err != nil { + return nil, err + } + } + + sendMessagesResponse, err := m.SendChatMessages(context.Background(), messages) + if err != nil { + return nil, err + } + + if err := response.Merge(sendMessagesResponse); err != nil { + return nil, err + } + + return response, nil +} + +func (m *Messenger) MyPendingRequestsToJoin() ([]*communities.RequestToJoin, error) { + return m.communitiesManager.PendingRequestsToJoinForUser(&m.identity.PublicKey) +} + +func (m *Messenger) PendingRequestsToJoinForCommunity(id types.HexBytes) ([]*communities.RequestToJoin, error) { + return m.communitiesManager.PendingRequestsToJoinForCommunity(id) +} + +func (m *Messenger) RemoveUserFromCommunity(id types.HexBytes, pkString string) (*MessengerResponse, error) { + publicKey, err := common.HexToPubkey(pkString) + if err != nil { + return nil, err + } + + community, err := m.communitiesManager.RemoveUserFromCommunity(id, publicKey) + if err != nil { + return nil, err + } + + response := &MessengerResponse{} + response.AddCommunity(community) + return response, nil +} diff --git a/protocol/messenger_config.go b/protocol/messenger_config.go index 906ad5d0f..f0c675031 100644 --- a/protocol/messenger_config.go +++ b/protocol/messenger_config.go @@ -20,7 +20,8 @@ type config struct { // This needs to be exposed until we move here mailserver logic // as otherwise the client is not notified of a new filter and // won't be pulling messages from mailservers until it reloads the chats/filters - onNegotiatedFilters func([]*transport.Filter) + onNegotiatedFilters func([]*transport.Filter) + onContactENSVerified func(*MessengerResponse) // systemMessagesTranslations holds translations for system-messages systemMessagesTranslations map[protobuf.MembershipUpdateEvent_EventType]string @@ -37,7 +38,9 @@ type config struct { mailserversDatabase *mailservers.Database account *multiaccounts.Account - verifyTransactionClient EthClient + verifyTransactionClient EthClient + verifyENSURL string + verifyENSContractAddress string pushNotificationServerConfig *pushnotificationserver.Config pushNotificationClientConfig *pushnotificationclient.Config @@ -155,3 +158,12 @@ func WithDeliveredHandler(h MessageDeliveredHandler) Option { return nil } } + +func WithENSVerificationConfig(onENSVerified func(*MessengerResponse), url, address string) Option { + return func(c *config) error { + c.onContactENSVerified = onENSVerified + c.verifyENSURL = url + c.verifyENSContractAddress = address + return nil + } +} diff --git a/protocol/messenger_contact_update_test.go b/protocol/messenger_contact_update_test.go index 5b208e1b8..51dbb5c2f 100644 --- a/protocol/messenger_contact_update_test.go +++ b/protocol/messenger_contact_update_test.go @@ -80,8 +80,8 @@ func (s *MessengerContactUpdateSuite) TestReceiveContactUpdate() { contact.SystemTags = []string{contactAdded} s.Require().NoError(theirMessenger.SaveContact(contact)) - s.Require().Len(response.Chats, 1) - chat := response.Chats[0] + s.Require().Len(response.Chats(), 1) + chat := response.Chats()[0] s.Require().False(chat.Active, "It does not create an active chat") // Wait for the message to reach its destination @@ -137,7 +137,7 @@ func (s *MessengerContactUpdateSuite) TestAddContact() { contact := response.Contacts[0] // It adds the profile chat and the one to one chat - s.Require().Len(response.Chats, 2) + s.Require().Len(response.Chats(), 2) // It should add the contact s.Require().True(contact.IsAdded()) diff --git a/protocol/messenger_contacts.go b/protocol/messenger_contacts.go index 5a4b29927..253830a7e 100644 --- a/protocol/messenger_contacts.go +++ b/protocol/messenger_contacts.go @@ -56,12 +56,10 @@ func (m *Messenger) AddContact(ctx context.Context, pubKey string) (*MessengerRe profileChat, ok := m.allChats[profileChatID] if !ok { - builtChat := CreateProfileChat(profileChatID, contact.ID, m.getTimesource()) - profileChat = &builtChat + profileChat = CreateProfileChat(profileChatID, contact.ID, m.getTimesource()) } - // TODO: return filters in messenger response - err = m.Join(*profileChat) + filters, err := m.Join(profileChat) if err != nil { return nil, err } @@ -73,7 +71,8 @@ func (m *Messenger) AddContact(ctx context.Context, pubKey string) (*MessengerRe return nil, err } - response.Chats = append(response.Chats, profileChat) + response.Filters = filters + response.AddChat(profileChat) publicKey, err := contact.PublicKey() if err != nil { @@ -291,7 +290,7 @@ func (m *Messenger) sendContactUpdate(ctx context.Context, chatID, ensName, prof } response.Contacts = []*Contact{contact} - response.Chats = []*Chat{chat} + response.AddChat(chat) chat.LastClockValue = clock err = m.saveChat(chat) diff --git a/protocol/messenger_emoji_test.go b/protocol/messenger_emoji_test.go index 0910f70a9..627ce17b8 100644 --- a/protocol/messenger_emoji_test.go +++ b/protocol/messenger_emoji_test.go @@ -74,21 +74,21 @@ func (s *MessengerEmojiSuite) TestSendEmoji() { chat := CreatePublicChat(chatID, alice.transport) - err = alice.SaveChat(&chat) + err = alice.SaveChat(chat) s.Require().NoError(err) - err = alice.Join(chat) + _, err = alice.Join(chat) s.Require().NoError(err) - err = bob.SaveChat(&chat) + err = bob.SaveChat(chat) s.Require().NoError(err) - err = bob.Join(chat) + _, err = bob.Join(chat) s.Require().NoError(err) // Send chat message from bob to alice - message := buildTestMessage(chat) + message := buildTestMessage(*chat) _, err = alice.SendChatMessage(context.Background(), message) s.NoError(err) @@ -153,7 +153,7 @@ func (s *MessengerEmojiSuite) TestEmojiPrivateGroup() { response, err := bob.CreateGroupChatWithMembers(context.Background(), "test", []string{}) s.NoError(err) - chat := response.Chats[0] + chat := response.Chats()[0] members := []string{types.EncodeHex(crypto.FromECDSAPub(&alice.identity.PublicKey))} _, err = bob.AddMembersToGroupChat(context.Background(), chat.ID, members) s.NoError(err) @@ -161,7 +161,7 @@ func (s *MessengerEmojiSuite) TestEmojiPrivateGroup() { // Retrieve their messages so that the chat is created _, err = WaitOnMessengerResponse( alice, - func(r *MessengerResponse) bool { return len(r.Chats) > 0 }, + func(r *MessengerResponse) bool { return len(r.Chats()) > 0 }, "chat invitation not received", ) s.Require().NoError(err) @@ -172,7 +172,7 @@ func (s *MessengerEmojiSuite) TestEmojiPrivateGroup() { // Wait for the message to reach its destination _, err = WaitOnMessengerResponse( bob, - func(r *MessengerResponse) bool { return len(r.Chats) > 0 }, + func(r *MessengerResponse) bool { return len(r.Chats()) > 0 }, "no joining group event received", ) s.Require().NoError(err) diff --git a/protocol/messenger_ens.go b/protocol/messenger_ens.go new file mode 100644 index 000000000..9658c6cbf --- /dev/null +++ b/protocol/messenger_ens.go @@ -0,0 +1,6 @@ +package protocol + +func (m *Messenger) ENSVerified(pubkey, ensName string) error { + clock := m.getTimesource().GetCurrentTime() + return m.ensVerifier.ENSVerified(pubkey, ensName, clock) +} diff --git a/protocol/messenger_installations_test.go b/protocol/messenger_installations_test.go index 84ce7513c..33d0f6289 100644 --- a/protocol/messenger_installations_test.go +++ b/protocol/messenger_installations_test.go @@ -77,8 +77,8 @@ func (s *MessengerInstallationSuite) TestReceiveInstallation() { response, err := theirMessenger.SendPairInstallation(context.Background()) s.Require().NoError(err) s.Require().NotNil(response) - s.Require().Len(response.Chats, 1) - s.Require().False(response.Chats[0].Active) + s.Require().Len(response.Chats(), 1) + s.Require().False(response.Chats()[0].Active) // Wait for the message to reach its destination response, err = WaitOnMessengerResponse( @@ -119,18 +119,18 @@ func (s *MessengerInstallationSuite) TestReceiveInstallation() { s.Require().True(actualContact.IsAdded()) chat := CreatePublicChat(statusChatID, s.m.transport) - err = s.m.SaveChat(&chat) + err = s.m.SaveChat(chat) s.Require().NoError(err) response, err = WaitOnMessengerResponse( theirMessenger, - func(r *MessengerResponse) bool { return len(r.Chats) > 0 }, + func(r *MessengerResponse) bool { return len(r.Chats()) > 0 }, "sync chat not received", ) s.Require().NoError(err) - actualChat := response.Chats[0] + actualChat := response.Chats()[0] s.Require().Equal(statusChatID, actualChat.ID) s.Require().True(actualChat.Active) s.Require().NoError(theirMessenger.Shutdown()) @@ -151,7 +151,7 @@ func (s *MessengerInstallationSuite) TestSyncInstallation() { // add chat chat := CreatePublicChat(statusChatID, s.m.transport) - err = s.m.SaveChat(&chat) + err = s.m.SaveChat(chat) s.Require().NoError(err) // pair @@ -166,8 +166,8 @@ func (s *MessengerInstallationSuite) TestSyncInstallation() { response, err := theirMessenger.SendPairInstallation(context.Background()) s.Require().NoError(err) s.Require().NotNil(response) - s.Require().Len(response.Chats, 1) - s.Require().False(response.Chats[0].Active) + s.Require().Len(response.Chats(), 1) + s.Require().False(response.Chats()[0].Active) // Wait for the message to reach its destination response, err = WaitOnMessengerResponse( @@ -200,7 +200,7 @@ func (s *MessengerInstallationSuite) TestSyncInstallation() { return err } - allChats = append(allChats, response.Chats...) + allChats = append(allChats, response.Chats()...) if len(allChats) >= 2 && len(response.Contacts) == 1 { actualContact = response.Contacts[0] @@ -243,8 +243,8 @@ func (s *MessengerInstallationSuite) TestSyncInstallationNewMessages() { response, err := bob2.SendPairInstallation(context.Background()) s.Require().NoError(err) s.Require().NotNil(response) - s.Require().Len(response.Chats, 1) - s.Require().False(response.Chats[0].Active) + s.Require().Len(response.Chats(), 1) + s.Require().False(response.Chats()[0].Active) // Wait for the message to reach its destination response, err = WaitOnMessengerResponse( @@ -263,9 +263,9 @@ func (s *MessengerInstallationSuite) TestSyncInstallationNewMessages() { alicePkString := types.EncodeHex(crypto.FromECDSAPub(&alice.identity.PublicKey)) chat := CreateOneToOneChat(alicePkString, &alice.identity.PublicKey, bob1.transport) - s.Require().NoError(bob1.SaveChat(&chat)) + s.Require().NoError(bob1.SaveChat(chat)) - inputMessage := buildTestMessage(chat) + inputMessage := buildTestMessage(*chat) _, err = s.m.SendChatMessage(context.Background(), inputMessage) s.Require().NoError(err) diff --git a/protocol/messenger_mute_test.go b/protocol/messenger_mute_test.go index e29261cda..94c5a2280 100644 --- a/protocol/messenger_mute_test.go +++ b/protocol/messenger_mute_test.go @@ -69,13 +69,13 @@ func (s *MessengerMuteSuite) TestSetMute() { chat := CreatePublicChat(chatID, s.m.transport) - err = s.m.SaveChat(&chat) + err = s.m.SaveChat(chat) s.Require().NoError(err) - err = s.m.Join(chat) + _, err = s.m.Join(chat) s.Require().NoError(err) - err = theirMessenger.SaveChat(&chat) + err = theirMessenger.SaveChat(chat) s.Require().NoError(err) s.Require().NoError(s.m.MuteChat(chatID)) diff --git a/protocol/messenger_response.go b/protocol/messenger_response.go index 8114365dc..b8e5e57f7 100644 --- a/protocol/messenger_response.go +++ b/protocol/messenger_response.go @@ -1,6 +1,8 @@ package protocol import ( + "encoding/json" + "github.com/status-im/status-go/protocol/common" "github.com/status-im/status-go/protocol/communities" "github.com/status-im/status-go/protocol/encryption/multidevice" @@ -9,70 +11,224 @@ import ( ) type MessengerResponse struct { - Chats []*Chat `json:"chats,omitempty"` - RemovedChats []string `json:"removedChats,omitempty"` - Messages []*common.Message `json:"messages,omitempty"` - Contacts []*Contact `json:"contacts,omitempty"` - Installations []*multidevice.Installation `json:"installations,omitempty"` - EmojiReactions []*EmojiReaction `json:"emojiReactions,omitempty"` - Invitations []*GroupChatInvitation `json:"invitations,omitempty"` - Communities []*communities.Community `json:"communities,omitempty"` - CommunityChanges []*communities.CommunityChanges `json:"communitiesChanges,omitempty"` - Filters []*transport.Filter `json:"filters,omitempty"` - RemovedFilters []*transport.Filter `json:"removedFilters,omitempty"` - Mailservers []mailservers.Mailserver `json:"mailservers,omitempty"` - MailserverTopics []mailservers.MailserverTopic `json:"mailserverTopics,omitempty"` - MailserverRanges []mailservers.ChatRequestRange `json:"mailserverRanges,omitempty"` - + Messages []*common.Message + Contacts []*Contact + Installations []*multidevice.Installation + EmojiReactions []*EmojiReaction + Invitations []*GroupChatInvitation + CommunityChanges []*communities.CommunityChanges + RequestsToJoinCommunity []*communities.RequestToJoin + Filters []*transport.Filter + RemovedFilters []*transport.Filter + Mailservers []mailservers.Mailserver + MailserverTopics []mailservers.MailserverTopic + MailserverRanges []mailservers.ChatRequestRange // Notifications a list of MessageNotificationBody derived from received messages that are useful to notify the user about - Notifications []MessageNotificationBody `json:"notifications"` + Notifications []MessageNotificationBody + + chats map[string]*Chat + removedChats map[string]bool + communities map[string]*communities.Community } -func (m *MessengerResponse) IsEmpty() bool { - return len(m.Chats)+len(m.Messages)+len(m.Contacts)+len(m.Installations)+len(m.Invitations)+len(m.EmojiReactions)+len(m.Communities)+len(m.CommunityChanges)+len(m.Filters)+len(m.RemovedFilters)+len(m.RemovedChats)+len(m.Notifications)+len(m.MailserverTopics)+len(m.Mailservers)+len(m.MailserverRanges) == 0 +func (r *MessengerResponse) MarshalJSON() ([]byte, error) { + responseItem := struct { + Chats []*Chat `json:"chats,omitempty"` + RemovedChats []string `json:"removedChats,omitempty"` + Messages []*common.Message `json:"messages,omitempty"` + Contacts []*Contact `json:"contacts,omitempty"` + Installations []*multidevice.Installation `json:"installations,omitempty"` + EmojiReactions []*EmojiReaction `json:"emojiReactions,omitempty"` + Invitations []*GroupChatInvitation `json:"invitations,omitempty"` + CommunityChanges []*communities.CommunityChanges `json:"communityChanges,omitempty"` + RequestsToJoinCommunity []*communities.RequestToJoin `json:"requestsToJoinCommunity,omitempty"` + Filters []*transport.Filter `json:"filters,omitempty"` + RemovedFilters []*transport.Filter `json:"removedFilters,omitempty"` + Mailservers []mailservers.Mailserver `json:"mailservers,omitempty"` + MailserverTopics []mailservers.MailserverTopic `json:"mailserverTopics,omitempty"` + MailserverRanges []mailservers.ChatRequestRange `json:"mailserverRanges,omitempty"` + // Notifications a list of MessageNotificationBody derived from received messages that are useful to notify the user about + Notifications []MessageNotificationBody `json:"notifications"` + Communities []*communities.Community `json:"communities,omitempty"` + }{ + Messages: r.Messages, + Contacts: r.Contacts, + Installations: r.Installations, + EmojiReactions: r.EmojiReactions, + Invitations: r.Invitations, + CommunityChanges: r.CommunityChanges, + RequestsToJoinCommunity: r.RequestsToJoinCommunity, + Filters: r.Filters, + RemovedFilters: r.RemovedFilters, + Mailservers: r.Mailservers, + MailserverTopics: r.MailserverTopics, + MailserverRanges: r.MailserverRanges, + Notifications: r.Notifications, + } + + responseItem.Chats = r.Chats() + responseItem.Communities = r.Communities() + responseItem.RemovedChats = r.RemovedChats() + + return json.Marshal(responseItem) +} + +func (r *MessengerResponse) Chats() []*Chat { + var chats []*Chat + for _, chat := range r.chats { + chats = append(chats, chat) + } + return chats +} + +func (r *MessengerResponse) RemovedChats() []string { + var chats []string + for chatID := range r.removedChats { + chats = append(chats, chatID) + } + return chats +} + +func (r *MessengerResponse) Communities() []*communities.Community { + var communities []*communities.Community + for _, c := range r.communities { + communities = append(communities, c) + } + return communities +} + +func (r *MessengerResponse) IsEmpty() bool { + return len(r.chats)+ + len(r.Messages)+ + len(r.Contacts)+ + len(r.Installations)+ + len(r.Invitations)+ + len(r.EmojiReactions)+ + len(r.communities)+ + len(r.CommunityChanges)+ + len(r.Filters)+ + len(r.RemovedFilters)+ + len(r.removedChats)+ + len(r.MailserverTopics)+ + len(r.Mailservers)+ + len(r.MailserverRanges)+ + len(r.Notifications)+ + len(r.RequestsToJoinCommunity) == 0 } // Merge takes another response and appends the new Chats & new Messages and replaces // the existing Messages & Chats if they have the same ID -func (m *MessengerResponse) Merge(response *MessengerResponse) error { - if len(response.Contacts)+len(response.Installations)+len(response.EmojiReactions)+len(response.Invitations) != 0 { +func (r *MessengerResponse) Merge(response *MessengerResponse) error { + if len(response.Contacts)+ + len(response.Installations)+ + len(response.EmojiReactions)+ + len(response.Invitations)+ + len(response.RequestsToJoinCommunity)+ + len(response.Mailservers)+ + len(response.MailserverTopics)+ + len(response.MailserverRanges)+ + len(response.Notifications)+ + len(response.EmojiReactions)+ + len(response.CommunityChanges) != 0 { return ErrNotImplemented } - m.overrideChats(response.Chats) - m.overrideMessages(response.Messages) + r.AddChats(response.Chats()) + r.AddRemovedChats(response.RemovedChats()) + r.overrideMessages(response.Messages) + r.overrideFilters(response.Filters) + r.overrideRemovedFilters(response.Filters) + r.AddCommunities(response.Communities()) return nil } -// overrideChats append new chats and override existing ones in response.Chats -func (m *MessengerResponse) overrideChats(chats []*Chat) { - for _, overrideChat := range chats { +// overrideMessages append new messages and override existing ones in response.Messages +func (r *MessengerResponse) overrideMessages(messages []*common.Message) { + for _, overrideMessage := range messages { var found = false - for idx, chat := range m.Chats { - if chat.ID == overrideChat.ID { - m.Chats[idx] = overrideChat + for idx, chat := range r.Messages { + if chat.ID == overrideMessage.ID { + r.Messages[idx] = overrideMessage found = true } } if !found { - m.Chats = append(m.Chats, overrideChat) + r.Messages = append(r.Messages, overrideMessage) } } } -// overrideMessages append new messages and override existing ones in response.Messages -func (m *MessengerResponse) overrideMessages(messages []*common.Message) { - for _, overrideMessage := range messages { +// overrideFilters append new filters and override existing ones in response.Filters +func (r *MessengerResponse) overrideFilters(filters []*transport.Filter) { + for _, overrideFilter := range filters { var found = false - for idx, chat := range m.Messages { - if chat.ID == overrideMessage.ID { - m.Messages[idx] = overrideMessage + for idx, filter := range r.Filters { + if filter.FilterID == overrideFilter.FilterID { + r.Filters[idx] = overrideFilter found = true } } if !found { - m.Messages = append(m.Messages, overrideMessage) + r.Filters = append(r.Filters, overrideFilter) } } } + +// overrideRemovedFilters append removed filters and override existing ones in response.Filters +func (r *MessengerResponse) overrideRemovedFilters(filters []*transport.Filter) { + for _, overrideFilter := range filters { + var found = false + for idx, filter := range r.RemovedFilters { + if filter.FilterID == overrideFilter.FilterID { + r.RemovedFilters[idx] = overrideFilter + found = true + } + } + if !found { + r.RemovedFilters = append(r.RemovedFilters, overrideFilter) + } + } +} + +func (r *MessengerResponse) AddCommunities(communities []*communities.Community) { + for _, overrideCommunity := range communities { + r.AddCommunity(overrideCommunity) + } +} + +func (r *MessengerResponse) AddCommunity(c *communities.Community) { + if r.communities == nil { + r.communities = make(map[string]*communities.Community) + } + + r.communities[c.IDString()] = c +} + +func (r *MessengerResponse) AddChat(c *Chat) { + if r.chats == nil { + r.chats = make(map[string]*Chat) + } + + r.chats[c.ID] = c +} + +func (r *MessengerResponse) AddChats(chats []*Chat) { + for _, c := range chats { + r.AddChat(c) + } +} + +func (r *MessengerResponse) AddRemovedChats(chats []string) { + for _, c := range chats { + r.AddRemovedChat(c) + } +} + +func (r *MessengerResponse) AddRemovedChat(chatID string) { + if r.removedChats == nil { + r.removedChats = make(map[string]bool) + } + + r.removedChats[chatID] = true +} diff --git a/protocol/messenger_response_test.go b/protocol/messenger_response_test.go index 197452ed0..beaeb3acb 100644 --- a/protocol/messenger_response_test.go +++ b/protocol/messenger_response_test.go @@ -13,19 +13,17 @@ func TestMessengerResponseMergeChats(t *testing.T) { chat1 := &Chat{ID: "1"} modifiedChat1 := &Chat{ID: "1", Name: "name"} chat2 := &Chat{ID: "3"} - response1 := &MessengerResponse{ - Chats: []*Chat{chat1}, - } + response1 := &MessengerResponse{} + response1.AddChat(chat1) - response2 := &MessengerResponse{ - Chats: []*Chat{modifiedChat1, chat2}, - } + response2 := &MessengerResponse{} + response2.AddChats([]*Chat{modifiedChat1, chat2}) require.NoError(t, response1.Merge(response2)) - require.Len(t, response1.Chats, 2) - require.Equal(t, modifiedChat1, response1.Chats[0]) - require.Equal(t, chat2, response1.Chats[1]) + require.Len(t, response1.Chats(), 2) + require.Equal(t, modifiedChat1, response1.chats[modifiedChat1.ID]) + require.Equal(t, chat2, response1.chats[chat2.ID]) } func TestMessengerResponseMergeMessages(t *testing.T) { diff --git a/protocol/messenger_test.go b/protocol/messenger_test.go index 12dad63dd..805745e5c 100644 --- a/protocol/messenger_test.go +++ b/protocol/messenger_test.go @@ -334,12 +334,12 @@ func buildTestMessage(chat Chat) *common.Message { func (s *MessengerSuite) TestMarkMessagesSeen() { chat := CreatePublicChat("test-chat", s.m.transport) chat.UnviewedMessagesCount = 2 - err := s.m.SaveChat(&chat) + err := s.m.SaveChat(chat) s.Require().NoError(err) - inputMessage1 := buildTestMessage(chat) + inputMessage1 := buildTestMessage(*chat) inputMessage1.ID = "1" inputMessage1.Seen = false - inputMessage2 := buildTestMessage(chat) + inputMessage2 := buildTestMessage(*chat) inputMessage2.ID = "2" inputMessage2.Seen = false @@ -363,12 +363,12 @@ func (s *MessengerSuite) TestMarkMessagesSeen() { func (s *MessengerSuite) TestMarkAllRead() { chat := CreatePublicChat("test-chat", s.m.transport) chat.UnviewedMessagesCount = 2 - err := s.m.SaveChat(&chat) + err := s.m.SaveChat(chat) s.Require().NoError(err) - inputMessage1 := buildTestMessage(chat) + inputMessage1 := buildTestMessage(*chat) inputMessage1.ID = "1" inputMessage1.Seen = false - inputMessage2 := buildTestMessage(chat) + inputMessage2 := buildTestMessage(*chat) inputMessage2.ID = "2" inputMessage2.Seen = false @@ -386,9 +386,9 @@ func (s *MessengerSuite) TestMarkAllRead() { func (s *MessengerSuite) TestSendPublic() { chat := CreatePublicChat("test-chat", s.m.transport) chat.LastClockValue = uint64(100000000000000) - err := s.m.SaveChat(&chat) + err := s.m.SaveChat(chat) s.NoError(err) - inputMessage := buildTestMessage(chat) + inputMessage := buildTestMessage(*chat) response, err := s.m.SendChatMessage(context.Background(), inputMessage) s.NoError(err) @@ -412,9 +412,9 @@ func (s *MessengerSuite) TestSendPublic() { func (s *MessengerSuite) TestSendProfile() { chat := CreateProfileChat("test-chat-profile", "0x"+hex.EncodeToString(crypto.FromECDSAPub(&s.privateKey.PublicKey)), s.m.transport) chat.LastClockValue = uint64(100000000000000) - err := s.m.SaveChat(&chat) + err := s.m.SaveChat(chat) s.NoError(err) - inputMessage := buildTestMessage(chat) + inputMessage := buildTestMessage(*chat) response, err := s.m.SendChatMessage(context.Background(), inputMessage) s.NoError(err) @@ -445,7 +445,7 @@ func (s *MessengerSuite) TestSendPrivateOneToOne() { inputMessage := &common.Message{} inputMessage.ChatId = chat.ID chat.LastClockValue = uint64(100000000000000) - err = s.m.SaveChat(&chat) + err = s.m.SaveChat(chat) s.NoError(err) response, err := s.m.SendChatMessage(context.Background(), inputMessage) s.NoError(err) @@ -466,9 +466,9 @@ func (s *MessengerSuite) TestSendPrivateOneToOne() { func (s *MessengerSuite) TestSendPrivateGroup() { response, err := s.m.CreateGroupChatWithMembers(context.Background(), "test", []string{}) s.NoError(err) - s.Require().Len(response.Chats, 1) + s.Require().Len(response.Chats(), 1) - chat := response.Chats[0] + chat := response.Chats()[0] key, err := crypto.GenerateKey() s.NoError(err) members := []string{"0x" + hex.EncodeToString(crypto.FromECDSAPub(&key.PublicKey))} @@ -499,9 +499,9 @@ func (s *MessengerSuite) TestSendPrivateGroup() { func (s *MessengerSuite) TestSendPrivateEmptyGroup() { response, err := s.m.CreateGroupChatWithMembers(context.Background(), "test", []string{}) s.NoError(err) - s.Require().Len(response.Chats, 1) + s.Require().Len(response.Chats(), 1) - chat := response.Chats[0] + chat := response.Chats()[0] inputMessage := &common.Message{} inputMessage.ChatId = chat.ID @@ -527,12 +527,12 @@ func (s *MessengerSuite) TestSendPrivateEmptyGroup() { // Make sure public messages sent by us are not func (s *MessengerSuite) TestRetrieveOwnPublic() { chat := CreatePublicChat("status", s.m.transport) - err := s.m.SaveChat(&chat) + err := s.m.SaveChat(chat) s.NoError(err) // Right-to-left text text := "پيل اندر خانه يي تاريک بود عرضه را آورده بودندش هنود i\nاز براي ديدنش مردم بسي اندر آن ظلمت همي شد هر کسي" - inputMessage := buildTestMessage(chat) + inputMessage := buildTestMessage(*chat) inputMessage.ChatId = chat.ID inputMessage.Text = text @@ -548,8 +548,8 @@ func (s *MessengerSuite) TestRetrieveOwnPublic() { s.True(textMessage.RTL) s.Equal(1, textMessage.LineCount) - s.Require().Len(response.Chats, 1) - actualChat := response.Chats[0] + s.Require().Len(response.Chats(), 1) + actualChat := response.Chats()[0] // It does not set the unviewed messages count s.Require().Equal(uint(0), actualChat.UnviewedMessagesCount) // It updates the last message clock value @@ -564,17 +564,17 @@ func (s *MessengerSuite) TestRetrieveTheirPublic() { _, err := theirMessenger.Start() s.Require().NoError(err) theirChat := CreatePublicChat("status", s.m.transport) - err = theirMessenger.SaveChat(&theirChat) + err = theirMessenger.SaveChat(theirChat) s.Require().NoError(err) chat := CreatePublicChat("status", s.m.transport) - err = s.m.SaveChat(&chat) + err = s.m.SaveChat(chat) s.Require().NoError(err) - err = s.m.Join(chat) + _, err = s.m.Join(chat) s.Require().NoError(err) - inputMessage := buildTestMessage(chat) + inputMessage := buildTestMessage(*chat) sendResponse, err := theirMessenger.SendChatMessage(context.Background(), inputMessage) s.NoError(err) @@ -591,8 +591,8 @@ func (s *MessengerSuite) TestRetrieveTheirPublic() { s.Require().NoError(err) s.Require().Len(response.Messages, 1) - s.Require().Len(response.Chats, 1) - actualChat := response.Chats[0] + s.Require().Len(response.Chats(), 1) + actualChat := response.Chats()[0] // It sets the unviewed messages count s.Require().Equal(uint(1), actualChat.UnviewedMessagesCount) // It updates the last message clock value @@ -607,23 +607,23 @@ func (s *MessengerSuite) TestDeletedAtClockValue() { _, err := theirMessenger.Start() s.Require().NoError(err) theirChat := CreatePublicChat("status", s.m.transport) - err = theirMessenger.SaveChat(&theirChat) + err = theirMessenger.SaveChat(theirChat) s.Require().NoError(err) chat := CreatePublicChat("status", s.m.transport) - err = s.m.SaveChat(&chat) + err = s.m.SaveChat(chat) s.Require().NoError(err) - err = s.m.Join(chat) + _, err = s.m.Join(chat) s.Require().NoError(err) - inputMessage := buildTestMessage(chat) + inputMessage := buildTestMessage(*chat) sentResponse, err := theirMessenger.SendChatMessage(context.Background(), inputMessage) s.NoError(err) chat.DeletedAtClockValue = sentResponse.Messages[0].Clock - err = s.m.SaveChat(&chat) + err = s.m.SaveChat(chat) s.Require().NoError(err) // Wait for the message to reach its destination @@ -639,28 +639,27 @@ func (s *MessengerSuite) TestRetrieveBlockedContact() { _, err := theirMessenger.Start() s.Require().NoError(err) theirChat := CreatePublicChat("status", s.m.transport) - err = theirMessenger.SaveChat(&theirChat) + err = theirMessenger.SaveChat(theirChat) s.Require().NoError(err) chat := CreatePublicChat("status", s.m.transport) - err = s.m.SaveChat(&chat) + err = s.m.SaveChat(chat) s.Require().NoError(err) - err = s.m.Join(chat) + _, err = s.m.Join(chat) s.Require().NoError(err) publicKeyHex := "0x" + hex.EncodeToString(crypto.FromECDSAPub(&theirMessenger.identity.PublicKey)) blockedContact := Contact{ - ID: publicKeyHex, - Name: "contact-name", - LastUpdated: 20, - SystemTags: []string{contactBlocked}, - TributeToTalk: "talk", + ID: publicKeyHex, + Name: "contact-name", + LastUpdated: 20, + SystemTags: []string{contactBlocked}, } s.Require().NoError(s.m.SaveContact(&blockedContact)) - inputMessage := buildTestMessage(chat) + inputMessage := buildTestMessage(*chat) _, err = theirMessenger.SendChatMessage(context.Background(), inputMessage) s.NoError(err) @@ -678,17 +677,17 @@ func (s *MessengerSuite) TestResendPublicMessage() { _, err := theirMessenger.Start() s.Require().NoError(err) theirChat := CreatePublicChat("status", s.m.transport) - err = theirMessenger.SaveChat(&theirChat) + err = theirMessenger.SaveChat(theirChat) s.Require().NoError(err) chat := CreatePublicChat("status", s.m.transport) - err = s.m.SaveChat(&chat) + err = s.m.SaveChat(chat) s.Require().NoError(err) - err = s.m.Join(chat) + _, err = s.m.Join(chat) s.Require().NoError(err) - inputMessage := buildTestMessage(chat) + inputMessage := buildTestMessage(*chat) sendResponse1, err := theirMessenger.SendChatMessage(context.Background(), inputMessage) s.Require().NoError(err) @@ -707,8 +706,8 @@ func (s *MessengerSuite) TestResendPublicMessage() { s.Require().NoError(err) s.Require().Len(response.Messages, 1) - s.Require().Len(response.Chats, 1) - actualChat := response.Chats[0] + s.Require().Len(response.Chats(), 1) + actualChat := response.Chats()[0] // It sets the unviewed messages count s.Require().Equal(uint(1), actualChat.UnviewedMessagesCount) // It updates the last message clock value @@ -733,17 +732,17 @@ func (s *MessengerSuite) TestRetrieveTheirPrivateChatExisting() { _, err := theirMessenger.Start() s.Require().NoError(err) theirChat := CreateOneToOneChat("XXX", &s.privateKey.PublicKey, s.m.transport) - err = theirMessenger.SaveChat(&theirChat) + err = theirMessenger.SaveChat(theirChat) s.Require().NoError(err) ourChat := CreateOneToOneChat("our-chat", &theirMessenger.identity.PublicKey, s.m.transport) ourChat.UnviewedMessagesCount = 1 // Make chat inactive ourChat.Active = false - err = s.m.SaveChat(&ourChat) + err = s.m.SaveChat(ourChat) s.Require().NoError(err) - inputMessage := buildTestMessage(theirChat) + inputMessage := buildTestMessage(*theirChat) sendResponse, err := theirMessenger.SendChatMessage(context.Background(), inputMessage) s.NoError(err) @@ -758,8 +757,8 @@ func (s *MessengerSuite) TestRetrieveTheirPrivateChatExisting() { ) s.Require().NoError(err) - s.Require().Equal(len(response.Chats), 1) - actualChat := response.Chats[0] + s.Require().Equal(len(response.Chats()), 1) + actualChat := response.Chats()[0] // It updates the unviewed messages count s.Require().Equal(uint(2), actualChat.UnviewedMessagesCount) // It updates the last message clock value @@ -776,10 +775,10 @@ func (s *MessengerSuite) TestRetrieveTheirPrivateChatNonExisting() { _, err := theirMessenger.Start() s.Require().NoError(err) chat := CreateOneToOneChat("XXX", &s.privateKey.PublicKey, s.m.transport) - err = theirMessenger.SaveChat(&chat) + err = theirMessenger.SaveChat(chat) s.NoError(err) - inputMessage := buildTestMessage(chat) + inputMessage := buildTestMessage(*chat) sendResponse, err := theirMessenger.SendChatMessage(context.Background(), inputMessage) s.NoError(err) @@ -796,8 +795,8 @@ func (s *MessengerSuite) TestRetrieveTheirPrivateChatNonExisting() { s.Require().NoError(err) - s.Require().Len(response.Chats, 1) - actualChat := response.Chats[0] + s.Require().Len(response.Chats(), 1) + actualChat := response.Chats()[0] // It updates the unviewed messages count s.Require().Equal(uint(1), actualChat.UnviewedMessagesCount) // It updates the last message clock value @@ -814,10 +813,10 @@ func (s *MessengerSuite) TestRetrieveTheirPublicChatNonExisting() { _, err := theirMessenger.Start() s.Require().NoError(err) chat := CreatePublicChat("test-chat", s.m.transport) - err = theirMessenger.SaveChat(&chat) + err = theirMessenger.SaveChat(chat) s.NoError(err) - inputMessage := buildTestMessage(chat) + inputMessage := buildTestMessage(*chat) sendResponse, err := theirMessenger.SendChatMessage(context.Background(), inputMessage) s.NoError(err) @@ -829,7 +828,7 @@ func (s *MessengerSuite) TestRetrieveTheirPublicChatNonExisting() { s.NoError(err) s.Require().Equal(len(response.Messages), 0) - s.Require().Equal(len(response.Chats), 0) + s.Require().Equal(len(response.Chats()), 0) s.Require().NoError(theirMessenger.Shutdown()) } @@ -841,9 +840,9 @@ func (s *MessengerSuite) TestRetrieveTheirPrivateGroupChat() { s.Require().NoError(err) response, err = s.m.CreateGroupChatWithMembers(context.Background(), "id", []string{}) s.NoError(err) - s.Require().Len(response.Chats, 1) + s.Require().Len(response.Chats(), 1) - ourChat := response.Chats[0] + ourChat := response.Chats()[0] err = s.m.SaveChat(ourChat) s.NoError(err) @@ -855,7 +854,7 @@ func (s *MessengerSuite) TestRetrieveTheirPrivateGroupChat() { // Retrieve their messages so that the chat is created _, err = WaitOnMessengerResponse( theirMessenger, - func(r *MessengerResponse) bool { return len(r.Chats) > 0 }, + func(r *MessengerResponse) bool { return len(r.Chats()) > 0 }, "chat invitation not received", ) s.Require().NoError(err) @@ -866,7 +865,7 @@ func (s *MessengerSuite) TestRetrieveTheirPrivateGroupChat() { // Wait for the message to reach its destination _, err = WaitOnMessengerResponse( s.m, - func(r *MessengerResponse) bool { return len(r.Chats) > 0 }, + func(r *MessengerResponse) bool { return len(r.Chats()) > 0 }, "no joining group event received", ) s.Require().NoError(err) @@ -886,8 +885,8 @@ func (s *MessengerSuite) TestRetrieveTheirPrivateGroupChat() { ) s.Require().NoError(err) - s.Require().Len(response.Chats, 1) - actualChat := response.Chats[0] + s.Require().Len(response.Chats(), 1) + actualChat := response.Chats()[0] // It updates the unviewed messages count s.Require().Equal(uint(1), actualChat.UnviewedMessagesCount) // It updates the last message clock value @@ -904,9 +903,9 @@ func (s *MessengerSuite) TestChangeNameGroupChat() { s.Require().NoError(err) response, err = s.m.CreateGroupChatWithMembers(context.Background(), "old-name", []string{}) s.NoError(err) - s.Require().Len(response.Chats, 1) + s.Require().Len(response.Chats(), 1) - ourChat := response.Chats[0] + ourChat := response.Chats()[0] err = s.m.SaveChat(ourChat) s.NoError(err) @@ -918,7 +917,7 @@ func (s *MessengerSuite) TestChangeNameGroupChat() { // Retrieve their messages so that the chat is created _, err = WaitOnMessengerResponse( theirMessenger, - func(r *MessengerResponse) bool { return len(r.Chats) > 0 }, + func(r *MessengerResponse) bool { return len(r.Chats()) > 0 }, "chat invitation not received", ) s.Require().NoError(err) @@ -929,7 +928,7 @@ func (s *MessengerSuite) TestChangeNameGroupChat() { // Wait for join group event _, err = WaitOnMessengerResponse( s.m, - func(r *MessengerResponse) bool { return len(r.Chats) > 0 }, + func(r *MessengerResponse) bool { return len(r.Chats()) > 0 }, "no joining group event received", ) s.Require().NoError(err) @@ -940,13 +939,13 @@ func (s *MessengerSuite) TestChangeNameGroupChat() { // Retrieve their messages so that the chat is created response, err = WaitOnMessengerResponse( theirMessenger, - func(r *MessengerResponse) bool { return len(r.Chats) > 0 }, + func(r *MessengerResponse) bool { return len(r.Chats()) > 0 }, "chat invitation not received", ) s.Require().NoError(err) - s.Require().Len(response.Chats, 1) - actualChat := response.Chats[0] + s.Require().Len(response.Chats(), 1) + actualChat := response.Chats()[0] s.Require().Equal(newName, actualChat.Name) s.Require().NoError(theirMessenger.Shutdown()) } @@ -959,9 +958,9 @@ func (s *MessengerSuite) TestReInvitedToGroupChat() { s.Require().NoError(err) response, err = s.m.CreateGroupChatWithMembers(context.Background(), "old-name", []string{}) s.NoError(err) - s.Require().Len(response.Chats, 1) + s.Require().Len(response.Chats(), 1) - ourChat := response.Chats[0] + ourChat := response.Chats()[0] err = s.m.SaveChat(ourChat) s.NoError(err) @@ -973,7 +972,7 @@ func (s *MessengerSuite) TestReInvitedToGroupChat() { // Retrieve their messages so that the chat is created _, err = WaitOnMessengerResponse( theirMessenger, - func(r *MessengerResponse) bool { return len(r.Chats) > 0 }, + func(r *MessengerResponse) bool { return len(r.Chats()) > 0 }, "chat invitation not received", ) s.Require().NoError(err) @@ -984,7 +983,7 @@ func (s *MessengerSuite) TestReInvitedToGroupChat() { // Wait for join group event _, err = WaitOnMessengerResponse( s.m, - func(r *MessengerResponse) bool { return len(r.Chats) > 0 }, + func(r *MessengerResponse) bool { return len(r.Chats()) > 0 }, "no joining group event received", ) s.Require().NoError(err) @@ -992,13 +991,13 @@ func (s *MessengerSuite) TestReInvitedToGroupChat() { response, err = theirMessenger.LeaveGroupChat(context.Background(), ourChat.ID, true) s.NoError(err) - s.Require().Len(response.Chats, 1) - s.Require().False(response.Chats[0].Active) + s.Require().Len(response.Chats(), 1) + s.Require().False(response.Chats()[0].Active) // Retrieve messages so user is removed _, err = WaitOnMessengerResponse( s.m, - func(r *MessengerResponse) bool { return len(r.Chats) > 0 && len(r.Chats[0].Members) == 1 }, + func(r *MessengerResponse) bool { return len(r.Chats()) > 0 && len(r.Chats()[0].Members) == 1 }, "leave group chat not received", ) @@ -1011,19 +1010,19 @@ func (s *MessengerSuite) TestReInvitedToGroupChat() { // Retrieve their messages so that the chat is created response, err = WaitOnMessengerResponse( theirMessenger, - func(r *MessengerResponse) bool { return len(r.Chats) > 0 }, + func(r *MessengerResponse) bool { return len(r.Chats()) > 0 }, "chat invitation not received", ) s.Require().NoError(err) - s.Require().Len(response.Chats, 1) - s.Require().True(response.Chats[0].Active) + s.Require().Len(response.Chats(), 1) + s.Require().True(response.Chats()[0].Active) s.Require().NoError(theirMessenger.Shutdown()) } func (s *MessengerSuite) TestChatPersistencePublic() { - chat := Chat{ + chat := &Chat{ ID: "chat-name", Name: "chat-name", Color: "#fffff", @@ -1036,19 +1035,18 @@ func (s *MessengerSuite) TestChatPersistencePublic() { LastMessage: &common.Message{}, } - s.Require().NoError(s.m.SaveChat(&chat)) + s.Require().NoError(s.m.SaveChat(chat)) savedChats := s.m.Chats() s.Require().Equal(1, len(savedChats)) actualChat := savedChats[0] - expectedChat := &chat - s.Require().Equal(actualChat, expectedChat) + s.Require().Equal(chat, actualChat) } func (s *MessengerSuite) TestDeleteChat() { chatID := "chatid" - chat := Chat{ + chat := &Chat{ ID: chatID, Name: "chat-name", Color: "#fffff", @@ -1061,7 +1059,7 @@ func (s *MessengerSuite) TestDeleteChat() { LastMessage: &common.Message{}, } - s.Require().NoError(s.m.SaveChat(&chat)) + s.Require().NoError(s.m.SaveChat(chat)) savedChats := s.m.Chats() s.Require().Equal(1, len(savedChats)) @@ -1071,7 +1069,7 @@ func (s *MessengerSuite) TestDeleteChat() { } func (s *MessengerSuite) TestChatPersistenceUpdate() { - chat := Chat{ + chat := &Chat{ ID: "chat-name", Name: "chat-name", Color: "#fffff", @@ -1084,28 +1082,26 @@ func (s *MessengerSuite) TestChatPersistenceUpdate() { LastMessage: &common.Message{}, } - s.Require().NoError(s.m.SaveChat(&chat)) + s.Require().NoError(s.m.SaveChat(chat)) savedChats := s.m.Chats() s.Require().Equal(1, len(savedChats)) actualChat := savedChats[0] - expectedChat := &chat - s.Require().Equal(expectedChat, actualChat) + s.Require().Equal(chat, actualChat) chat.Name = "updated-name-1" - s.Require().NoError(s.m.SaveChat(&chat)) + s.Require().NoError(s.m.SaveChat(chat)) updatedChats := s.m.Chats() s.Require().Equal(1, len(updatedChats)) actualUpdatedChat := updatedChats[0] - expectedUpdatedChat := &chat - s.Require().Equal(expectedUpdatedChat, actualUpdatedChat) + s.Require().Equal(chat, actualUpdatedChat) } func (s *MessengerSuite) TestChatPersistenceOneToOne() { - chat := Chat{ + chat := &Chat{ ID: testPK, Name: testPK, Color: "#fffff", @@ -1127,20 +1123,19 @@ func (s *MessengerSuite) TestChatPersistenceOneToOne() { pk, err := crypto.UnmarshalPubkey(publicKeyBytes) s.Require().NoError(err) - s.Require().NoError(s.m.SaveChat(&chat)) + s.Require().NoError(s.m.SaveChat(chat)) s.Require().NoError(s.m.SaveContact(&contact)) savedChats := s.m.Chats() s.Require().Equal(1, len(savedChats)) actualChat := savedChats[0] - expectedChat := &chat actualPk, err := actualChat.PublicKey() s.Require().NoError(err) s.Require().Equal(pk, actualPk) - s.Require().Equal(expectedChat, actualChat) + s.Require().Equal(chat, actualChat) s.Require().NotEmpty(actualChat.Identicon) s.Require().NotEmpty(actualChat.Alias) } @@ -1159,7 +1154,7 @@ func (s *MessengerSuite) TestChatPersistencePrivateGroupChat() { s.Require().NoError(err) member3ID := types.EncodeHex(crypto.FromECDSAPub(&member3Key.PublicKey)) - chat := Chat{ + chat := &Chat{ ID: "chat-id", Name: "chat-id", Color: "#fffff", @@ -1206,14 +1201,13 @@ func (s *MessengerSuite) TestChatPersistencePrivateGroupChat() { UnviewedMessagesCount: 40, LastMessage: &common.Message{}, } - s.Require().NoError(s.m.SaveChat(&chat)) + s.Require().NoError(s.m.SaveChat(chat)) savedChats := s.m.Chats() s.Require().Equal(1, len(savedChats)) actualChat := savedChats[0] - expectedChat := &chat - s.Require().Equal(expectedChat, actualChat) + s.Require().Equal(chat, actualChat) } func (s *MessengerSuite) TestBlockContact() { @@ -1234,10 +1228,9 @@ func (s *MessengerSuite) TestBlockContact() { FCMToken: "token-2", }, }, - TributeToTalk: "talk", } - chat1 := Chat{ + chat1 := &Chat{ ID: contact.ID, Name: "chat-name", Color: "#fffff", @@ -1249,7 +1242,7 @@ func (s *MessengerSuite) TestBlockContact() { UnviewedMessagesCount: 40, } - chat2 := Chat{ + chat2 := &Chat{ ID: "chat-2", Name: "chat-name", Color: "#fffff", @@ -1261,7 +1254,7 @@ func (s *MessengerSuite) TestBlockContact() { UnviewedMessagesCount: 40, } - chat3 := Chat{ + chat3 := &Chat{ ID: "chat-3", Name: "chat-name", Color: "#fffff", @@ -1273,9 +1266,9 @@ func (s *MessengerSuite) TestBlockContact() { UnviewedMessagesCount: 40, } - s.Require().NoError(s.m.SaveChat(&chat1)) - s.Require().NoError(s.m.SaveChat(&chat2)) - s.Require().NoError(s.m.SaveChat(&chat3)) + s.Require().NoError(s.m.SaveChat(chat1)) + s.Require().NoError(s.m.SaveChat(chat2)) + s.Require().NoError(s.m.SaveChat(chat3)) s.Require().NoError(s.m.SaveContact(&contact)) @@ -1416,7 +1409,6 @@ func (s *MessengerSuite) TestContactPersistence() { FCMToken: "token-2", }, }, - TributeToTalk: "talk", } s.Require().NoError(s.m.SaveContact(&contact)) @@ -1450,7 +1442,6 @@ func (s *MessengerSuite) TestContactPersistenceUpdate() { FCMToken: "token-2", }, }, - TributeToTalk: "talk", } s.Require().NoError(s.m.SaveContact(&contact)) @@ -1485,9 +1476,9 @@ func (s *MessengerSuite) TestCreateGroupChatWithMembers() { members := []string{testPK} response, err := s.m.CreateGroupChatWithMembers(context.Background(), "test", members) s.NoError(err) - s.Require().Len(response.Chats, 1) + s.Require().Len(response.Chats(), 1) - chat := response.Chats[0] + chat := response.Chats()[0] s.Require().Equal("test", chat.Name) publicKeyHex := "0x" + hex.EncodeToString(crypto.FromECDSAPub(&s.m.identity.PublicKey)) @@ -1499,9 +1490,9 @@ func (s *MessengerSuite) TestCreateGroupChatWithMembers() { func (s *MessengerSuite) TestAddMembersToChat() { response, err := s.m.CreateGroupChatWithMembers(context.Background(), "test", []string{}) s.Require().NoError(err) - s.Require().Len(response.Chats, 1) + s.Require().Len(response.Chats(), 1) - chat := response.Chats[0] + chat := response.Chats()[0] key, err := crypto.GenerateKey() s.Require().NoError(err) @@ -1509,10 +1500,10 @@ func (s *MessengerSuite) TestAddMembersToChat() { response, err = s.m.AddMembersToGroupChat(context.Background(), chat.ID, members) s.Require().NoError(err) - s.Require().Len(response.Chats, 1) + s.Require().Len(response.Chats(), 1) s.Require().Len(response.Messages, 1) - chat = response.Chats[0] + chat = response.Chats()[0] publicKeyHex := "0x" + hex.EncodeToString(crypto.FromECDSAPub(&s.m.identity.PublicKey)) keyHex := "0x" + hex.EncodeToString(crypto.FromECDSAPub(&key.PublicKey)) @@ -1528,7 +1519,7 @@ func (s *MessengerSuite) TestDeclineRequestAddressForTransaction() { theirPkString := types.EncodeHex(crypto.FromECDSAPub(&theirMessenger.identity.PublicKey)) chat := CreateOneToOneChat(theirPkString, &theirMessenger.identity.PublicKey, s.m.transport) - err = s.m.SaveChat(&chat) + err = s.m.SaveChat(chat) s.Require().NoError(err) myAddress := crypto.PubkeyToAddress(s.m.identity.PublicKey) @@ -1536,7 +1527,7 @@ func (s *MessengerSuite) TestDeclineRequestAddressForTransaction() { response, err := s.m.RequestAddressForTransaction(context.Background(), theirPkString, myAddress.Hex(), value, contract) s.Require().NoError(err) s.Require().NotNil(response) - s.Require().Len(response.Chats, 1) + s.Require().Len(response.Chats(), 1) s.Require().Len(response.Messages, 1) senderMessage := response.Messages[0] @@ -1559,7 +1550,7 @@ func (s *MessengerSuite) TestDeclineRequestAddressForTransaction() { s.Require().NoError(err) s.Require().NotNil(response) - s.Require().Len(response.Chats, 1) + s.Require().Len(response.Chats(), 1) s.Require().Len(response.Messages, 1) receiverMessage := response.Messages[0] @@ -1574,7 +1565,7 @@ func (s *MessengerSuite) TestDeclineRequestAddressForTransaction() { // We decline the request response, err = theirMessenger.DeclineRequestAddressForTransaction(context.Background(), receiverMessage.ID) s.Require().NoError(err) - s.Require().Len(response.Chats, 1) + s.Require().Len(response.Chats(), 1) s.Require().Len(response.Messages, 1) senderMessage = response.Messages[0] @@ -1595,7 +1586,7 @@ func (s *MessengerSuite) TestDeclineRequestAddressForTransaction() { ) s.Require().NoError(err) - s.Require().Len(response.Chats, 1) + s.Require().Len(response.Chats(), 1) s.Require().Len(response.Messages, 1) receiverMessage = response.Messages[0] @@ -1623,7 +1614,7 @@ func (s *MessengerSuite) TestSendEthTransaction() { receiverAddressString := strings.ToLower(receiverAddress.Hex()) chat := CreateOneToOneChat(theirPkString, &theirMessenger.identity.PublicKey, s.m.transport) - err = s.m.SaveChat(&chat) + err = s.m.SaveChat(chat) s.Require().NoError(err) transactionHash := testTransactionHash @@ -1633,7 +1624,7 @@ func (s *MessengerSuite) TestSendEthTransaction() { response, err := s.m.SendTransaction(context.Background(), theirPkString, value, contract, transactionHash, signature) s.Require().NoError(err) s.Require().NotNil(response) - s.Require().Len(response.Chats, 1) + s.Require().Len(response.Chats(), 1) s.Require().Len(response.Messages, 1) senderMessage := response.Messages[0] @@ -1695,7 +1686,7 @@ func (s *MessengerSuite) TestSendEthTransaction() { s.Require().NoError(err) s.Require().NotNil(response) - s.Require().Len(response.Chats, 1) + s.Require().Len(response.Chats(), 1) s.Require().Len(response.Messages, 1) receiverMessage := response.Messages[0] @@ -1727,7 +1718,7 @@ func (s *MessengerSuite) TestSendTokenTransaction() { receiverAddressString := strings.ToLower(receiverAddress.Hex()) chat := CreateOneToOneChat(theirPkString, &theirMessenger.identity.PublicKey, s.m.transport) - err = s.m.SaveChat(&chat) + err = s.m.SaveChat(chat) s.Require().NoError(err) transactionHash := testTransactionHash @@ -1737,7 +1728,7 @@ func (s *MessengerSuite) TestSendTokenTransaction() { response, err := s.m.SendTransaction(context.Background(), theirPkString, value, contract, transactionHash, signature) s.Require().NoError(err) s.Require().NotNil(response) - s.Require().Len(response.Chats, 1) + s.Require().Len(response.Chats(), 1) s.Require().Len(response.Messages, 1) senderMessage := response.Messages[0] @@ -1799,7 +1790,7 @@ func (s *MessengerSuite) TestSendTokenTransaction() { s.Require().NoError(err) s.Require().NotNil(response) - s.Require().Len(response.Chats, 1) + s.Require().Len(response.Chats(), 1) s.Require().Len(response.Messages, 1) receiverMessage := response.Messages[0] @@ -1829,13 +1820,13 @@ func (s *MessengerSuite) TestAcceptRequestAddressForTransaction() { myAddress := crypto.PubkeyToAddress(s.m.identity.PublicKey) chat := CreateOneToOneChat(theirPkString, &theirMessenger.identity.PublicKey, s.m.transport) - err = s.m.SaveChat(&chat) + err = s.m.SaveChat(chat) s.Require().NoError(err) response, err := s.m.RequestAddressForTransaction(context.Background(), theirPkString, myAddress.Hex(), value, contract) s.Require().NoError(err) s.Require().NotNil(response) - s.Require().Len(response.Chats, 1) + s.Require().Len(response.Chats(), 1) s.Require().Len(response.Messages, 1) senderMessage := response.Messages[0] @@ -1858,7 +1849,7 @@ func (s *MessengerSuite) TestAcceptRequestAddressForTransaction() { s.Require().NoError(err) s.Require().NotNil(response) - s.Require().Len(response.Chats, 1) + s.Require().Len(response.Chats(), 1) s.Require().Len(response.Messages, 1) receiverMessage := response.Messages[0] @@ -1873,7 +1864,7 @@ func (s *MessengerSuite) TestAcceptRequestAddressForTransaction() { // We accept the request response, err = theirMessenger.AcceptRequestAddressForTransaction(context.Background(), receiverMessage.ID, "some-address") s.Require().NoError(err) - s.Require().Len(response.Chats, 1) + s.Require().Len(response.Chats(), 1) s.Require().Len(response.Messages, 1) senderMessage = response.Messages[0] @@ -1895,7 +1886,7 @@ func (s *MessengerSuite) TestAcceptRequestAddressForTransaction() { ) s.Require().NoError(err) - s.Require().Len(response.Chats, 1) + s.Require().Len(response.Chats(), 1) s.Require().Len(response.Messages, 1) receiverMessage = response.Messages[0] @@ -1922,13 +1913,13 @@ func (s *MessengerSuite) TestDeclineRequestTransaction() { theirPkString := types.EncodeHex(crypto.FromECDSAPub(&theirMessenger.identity.PublicKey)) chat := CreateOneToOneChat(theirPkString, &theirMessenger.identity.PublicKey, s.m.transport) - err = s.m.SaveChat(&chat) + err = s.m.SaveChat(chat) s.Require().NoError(err) response, err := s.m.RequestTransaction(context.Background(), theirPkString, value, contract, receiverAddressString) s.Require().NoError(err) s.Require().NotNil(response) - s.Require().Len(response.Chats, 1) + s.Require().Len(response.Chats(), 1) s.Require().Len(response.Messages, 1) senderMessage := response.Messages[0] @@ -1952,7 +1943,7 @@ func (s *MessengerSuite) TestDeclineRequestTransaction() { s.Require().NoError(err) s.Require().NotNil(response) - s.Require().Len(response.Chats, 1) + s.Require().Len(response.Chats(), 1) s.Require().Len(response.Messages, 1) receiverMessage := response.Messages[0] @@ -1968,7 +1959,7 @@ func (s *MessengerSuite) TestDeclineRequestTransaction() { response, err = theirMessenger.DeclineRequestTransaction(context.Background(), initialCommandID) s.Require().NoError(err) s.Require().NotNil(response) - s.Require().Len(response.Chats, 1) + s.Require().Len(response.Chats(), 1) s.Require().Len(response.Messages, 1) senderMessage = response.Messages[0] @@ -1988,7 +1979,7 @@ func (s *MessengerSuite) TestDeclineRequestTransaction() { s.Require().NoError(err) s.Require().NotNil(response) - s.Require().Len(response.Chats, 1) + s.Require().Len(response.Chats(), 1) s.Require().Len(response.Messages, 1) receiverMessage = response.Messages[0] @@ -2012,13 +2003,13 @@ func (s *MessengerSuite) TestRequestTransaction() { theirPkString := types.EncodeHex(crypto.FromECDSAPub(&theirMessenger.identity.PublicKey)) chat := CreateOneToOneChat(theirPkString, &theirMessenger.identity.PublicKey, s.m.transport) - err = s.m.SaveChat(&chat) + err = s.m.SaveChat(chat) s.Require().NoError(err) response, err := s.m.RequestTransaction(context.Background(), theirPkString, value, contract, receiverAddressString) s.Require().NoError(err) s.Require().NotNil(response) - s.Require().Len(response.Chats, 1) + s.Require().Len(response.Chats(), 1) s.Require().Len(response.Messages, 1) senderMessage := response.Messages[0] @@ -2042,7 +2033,7 @@ func (s *MessengerSuite) TestRequestTransaction() { s.Require().NoError(err) s.Require().NotNil(response) - s.Require().Len(response.Chats, 1) + s.Require().Len(response.Chats(), 1) s.Require().Len(response.Messages, 1) receiverMessage := response.Messages[0] @@ -2061,7 +2052,7 @@ func (s *MessengerSuite) TestRequestTransaction() { response, err = theirMessenger.AcceptRequestTransaction(context.Background(), transactionHash, initialCommandID, signature) s.Require().NoError(err) s.Require().NotNil(response) - s.Require().Len(response.Chats, 1) + s.Require().Len(response.Chats(), 1) s.Require().Len(response.Messages, 1) senderMessage = response.Messages[0] @@ -2128,7 +2119,7 @@ func (s *MessengerSuite) TestRequestTransaction() { s.Require().NoError(err) s.Require().NotNil(response) - s.Require().Len(response.Chats, 1) + s.Require().Len(response.Chats(), 1) s.Require().Len(response.Messages, 1) receiverMessage = response.Messages[0] @@ -2225,9 +2216,9 @@ func (s *MessengerSuite) TestSentEventTracking() { //when message sent, its sent field should be "false" until we got confirmation chat := CreatePublicChat("test-chat", s.m.transport) - err := s.m.SaveChat(&chat) + err := s.m.SaveChat(chat) s.NoError(err) - inputMessage := buildTestMessage(chat) + inputMessage := buildTestMessage(*chat) _, err = s.m.SendChatMessage(context.Background(), inputMessage) s.NoError(err) @@ -2248,9 +2239,9 @@ func (s *MessengerSuite) TestSentEventTracking() { func (s *MessengerSuite) TestLastSentField() { //send message chat := CreatePublicChat("test-chat", s.m.transport) - err := s.m.SaveChat(&chat) + err := s.m.SaveChat(chat) s.NoError(err) - inputMessage := buildTestMessage(chat) + inputMessage := buildTestMessage(*chat) _, err = s.m.SendChatMessage(context.Background(), inputMessage) s.NoError(err) @@ -2335,9 +2326,9 @@ func (s *MessengerSuite) TestShouldResendEmoji() { func (s *MessengerSuite) TestMessageSent() { //send message chat := CreatePublicChat("test-chat", s.m.transport) - err := s.m.SaveChat(&chat) + err := s.m.SaveChat(chat) s.NoError(err) - inputMessage := buildTestMessage(chat) + inputMessage := buildTestMessage(*chat) _, err = s.m.SendChatMessage(context.Background(), inputMessage) s.NoError(err) @@ -2360,9 +2351,9 @@ func (s *MessengerSuite) TestMessageSent() { func (s *MessengerSuite) TestResendExpiredEmojis() { //send message chat := CreatePublicChat("test-chat", s.m.transport) - err := s.m.SaveChat(&chat) + err := s.m.SaveChat(chat) s.NoError(err) - inputMessage := buildTestMessage(chat) + inputMessage := buildTestMessage(*chat) _, err = s.m.SendChatMessage(context.Background(), inputMessage) s.NoError(err) @@ -2431,7 +2422,7 @@ func (s *MessageHandlerSuite) TestRun() { testCases := []struct { Name string Error bool - Chat Chat // Chat to create + Chat *Chat // Chat to create Message common.Message SigPubKey *ecdsa.PublicKey ExpectedChatID string @@ -2518,8 +2509,8 @@ func (s *MessageHandlerSuite) TestRun() { for idx, tc := range testCases { s.Run(tc.Name, func() { chatsMap := make(map[string]*Chat) - if tc.Chat.ID != "" { - chatsMap[tc.Chat.ID] = &tc.Chat + if tc.Chat != nil && tc.Chat.ID != "" { + chatsMap[tc.Chat.ID] = tc.Chat } message := tc.Message diff --git a/protocol/migrations/migrations.go b/protocol/migrations/migrations.go index ba7959547..c5edc42db 100644 --- a/protocol/migrations/migrations.go +++ b/protocol/migrations/migrations.go @@ -24,6 +24,7 @@ // 1610959908_add_dont_wrap_to_raw_messages.up.sql (83B) // 1610960912_add_send_on_personal_topic.up.sql (82B) // 1612870480_add_datasync_id.up.sql (111B) +// 1614152139_add_communities_request_to_join.up.sql (831B) // README.md (554B) // doc.go (850B) @@ -45,7 +46,7 @@ import ( func bindataRead(data []byte, name string) ([]byte, error) { gz, err := gzip.NewReader(bytes.NewBuffer(data)) if err != nil { - return nil, fmt.Errorf("read %q: %w", name, err) + return nil, fmt.Errorf("read %q: %v", name, err) } var buf bytes.Buffer @@ -53,7 +54,7 @@ func bindataRead(data []byte, name string) ([]byte, error) { clErr := gz.Close() if err != nil { - return nil, fmt.Errorf("read %q: %w", name, err) + return nil, fmt.Errorf("read %q: %v", name, err) } if clErr != nil { return nil, err @@ -109,7 +110,7 @@ func _000001_initDownDbSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "000001_init.down.db.sql", size: 65, mode: os.FileMode(0644), modTime: time.Unix(1608547984, 0)} + info := bindataFileInfo{name: "000001_init.down.db.sql", size: 65, mode: os.FileMode(0644), modTime: time.Unix(1610007618, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x5e, 0xbb, 0x3f, 0x1, 0x75, 0x19, 0x70, 0x86, 0xa7, 0x34, 0x40, 0x17, 0x34, 0x3e, 0x18, 0x51, 0x79, 0xd4, 0x22, 0xad, 0x8f, 0x80, 0xcc, 0xa6, 0xcc, 0x6, 0x2b, 0x62, 0x2, 0x47, 0xba, 0xf9}} return a, nil } @@ -129,7 +130,7 @@ func _000001_initUpDbSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "000001_init.up.db.sql", size: 2719, mode: os.FileMode(0644), modTime: time.Unix(1608547984, 0)} + info := bindataFileInfo{name: "000001_init.up.db.sql", size: 2719, mode: os.FileMode(0644), modTime: time.Unix(1610007618, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x60, 0xdc, 0xeb, 0xe, 0xc2, 0x4f, 0x75, 0xa, 0xf6, 0x3e, 0xc7, 0xc4, 0x4, 0xe2, 0xe1, 0xa4, 0x73, 0x2f, 0x4a, 0xad, 0x1a, 0x0, 0xc3, 0x93, 0x9d, 0x77, 0x3e, 0x31, 0x91, 0x77, 0x2e, 0xc8}} return a, nil } @@ -149,7 +150,7 @@ func _000002_add_last_ens_clock_valueUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "000002_add_last_ens_clock_value.up.sql", size: 77, mode: os.FileMode(0644), modTime: time.Unix(1608547984, 0)} + info := bindataFileInfo{name: "000002_add_last_ens_clock_value.up.sql", size: 77, mode: os.FileMode(0644), modTime: time.Unix(1610007618, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x4d, 0x3, 0x8f, 0xd5, 0x85, 0x83, 0x47, 0xbe, 0xf9, 0x82, 0x7e, 0x81, 0xa4, 0xbd, 0xaa, 0xd5, 0x98, 0x18, 0x5, 0x2d, 0x82, 0x42, 0x3b, 0x3, 0x50, 0xc3, 0x1e, 0x84, 0x35, 0xf, 0xb6, 0x2b}} return a, nil } @@ -169,7 +170,7 @@ func _1586358095_add_replaceUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "1586358095_add_replace.up.sql", size: 224, mode: os.FileMode(0644), modTime: time.Unix(1608547984, 0)} + info := bindataFileInfo{name: "1586358095_add_replace.up.sql", size: 224, mode: os.FileMode(0644), modTime: time.Unix(1611588719, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xd2, 0xb3, 0xa9, 0xc7, 0x7f, 0x9d, 0x8f, 0x43, 0x8c, 0x9e, 0x58, 0x8d, 0x44, 0xbc, 0xfa, 0x6b, 0x5f, 0x3f, 0x5a, 0xbe, 0xe8, 0xb1, 0x16, 0xf, 0x91, 0x2a, 0xa0, 0x71, 0xbb, 0x8d, 0x6b, 0xcb}} return a, nil } @@ -189,7 +190,7 @@ func _1588665364_add_image_dataUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "1588665364_add_image_data.up.sql", size: 186, mode: os.FileMode(0644), modTime: time.Unix(1608547984, 0)} + info := bindataFileInfo{name: "1588665364_add_image_data.up.sql", size: 186, mode: os.FileMode(0644), modTime: time.Unix(1611588719, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xd6, 0xc6, 0x35, 0xb4, 0x4c, 0x39, 0x96, 0x29, 0x30, 0xda, 0xf4, 0x8f, 0xcb, 0xf1, 0x9f, 0x84, 0xdc, 0x88, 0xd4, 0xd5, 0xbc, 0xb6, 0x5b, 0x46, 0x78, 0x67, 0x76, 0x1a, 0x5, 0x36, 0xdc, 0xe5}} return a, nil } @@ -209,7 +210,7 @@ func _1589365189_add_pow_targetUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "1589365189_add_pow_target.up.sql", size: 66, mode: os.FileMode(0644), modTime: time.Unix(1608547984, 0)} + info := bindataFileInfo{name: "1589365189_add_pow_target.up.sql", size: 66, mode: os.FileMode(0644), modTime: time.Unix(1611588719, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x4e, 0x3a, 0xe2, 0x2e, 0x7d, 0xaf, 0xbb, 0xcc, 0x21, 0xa1, 0x7a, 0x41, 0x9a, 0xd0, 0xbb, 0xa9, 0xc8, 0x35, 0xf9, 0x32, 0x34, 0x46, 0x44, 0x9a, 0x86, 0x40, 0x7c, 0xb9, 0x23, 0xc7, 0x3, 0x3f}} return a, nil } @@ -229,7 +230,7 @@ func _1591277220_add_index_messagesUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "1591277220_add_index_messages.up.sql", size: 240, mode: os.FileMode(0644), modTime: time.Unix(1608547984, 0)} + info := bindataFileInfo{name: "1591277220_add_index_messages.up.sql", size: 240, mode: os.FileMode(0644), modTime: time.Unix(1614068874, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x9c, 0xfe, 0xbe, 0xd5, 0xb8, 0x8f, 0xdd, 0xef, 0xbb, 0xa8, 0xad, 0x7f, 0xed, 0x5b, 0x5b, 0x2f, 0xe6, 0x82, 0x27, 0x78, 0x1f, 0xb9, 0x57, 0xdc, 0x8, 0xc2, 0xb2, 0xa9, 0x9a, 0x4, 0xe1, 0x7a}} return a, nil } @@ -249,7 +250,7 @@ func _1593087212_add_mute_chat_and_raw_message_fieldsUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "1593087212_add_mute_chat_and_raw_message_fields.up.sql", size: 215, mode: os.FileMode(0644), modTime: time.Unix(1608547984, 0)} + info := bindataFileInfo{name: "1593087212_add_mute_chat_and_raw_message_fields.up.sql", size: 215, mode: os.FileMode(0644), modTime: time.Unix(1611588835, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x73, 0x99, 0x61, 0xd1, 0xaa, 0xb4, 0xbf, 0xaf, 0xd7, 0x20, 0x17, 0x40, 0xf9, 0x2, 0xfb, 0xcc, 0x40, 0x2a, 0xd, 0x86, 0x36, 0x30, 0x88, 0x89, 0x25, 0x80, 0x42, 0xb0, 0x5b, 0xe9, 0x73, 0x78}} return a, nil } @@ -269,7 +270,7 @@ func _1595862781_add_audio_dataUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "1595862781_add_audio_data.up.sql", size: 246, mode: os.FileMode(0644), modTime: time.Unix(1608547984, 0)} + info := bindataFileInfo{name: "1595862781_add_audio_data.up.sql", size: 246, mode: os.FileMode(0644), modTime: time.Unix(1611588835, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xae, 0xd2, 0xee, 0x55, 0xfb, 0x36, 0xa4, 0x92, 0x66, 0xe, 0x81, 0x62, 0x1e, 0x7a, 0x69, 0xa, 0xd5, 0x4b, 0xa5, 0x6a, 0x8d, 0x1d, 0xce, 0xf3, 0x3e, 0xc0, 0x5f, 0x9c, 0x66, 0x1b, 0xb4, 0xed}} return a, nil } @@ -289,7 +290,7 @@ func _1595865249_create_emoji_reactions_tableUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "1595865249_create_emoji_reactions_table.up.sql", size: 300, mode: os.FileMode(0644), modTime: time.Unix(1608547984, 0)} + info := bindataFileInfo{name: "1595865249_create_emoji_reactions_table.up.sql", size: 300, mode: os.FileMode(0644), modTime: time.Unix(1611588835, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x3e, 0xc5, 0x43, 0x5c, 0x3d, 0x53, 0x43, 0x2c, 0x1a, 0xa5, 0xb6, 0xbf, 0x7, 0x4, 0x5a, 0x3e, 0x40, 0x8b, 0xa4, 0x57, 0x12, 0x58, 0xbc, 0x42, 0xe2, 0xc3, 0xde, 0x76, 0x98, 0x80, 0xe2, 0xbe}} return a, nil } @@ -309,7 +310,7 @@ func _1596805115_create_group_chat_invitations_tableUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "1596805115_create_group_chat_invitations_table.up.sql", size: 231, mode: os.FileMode(0644), modTime: time.Unix(1608547984, 0)} + info := bindataFileInfo{name: "1596805115_create_group_chat_invitations_table.up.sql", size: 231, mode: os.FileMode(0644), modTime: time.Unix(1611588835, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x6d, 0xb1, 0x14, 0x6d, 0x54, 0x28, 0x67, 0xc3, 0x23, 0x6a, 0xfc, 0x80, 0xdf, 0x9e, 0x4c, 0x35, 0x36, 0xf, 0xf8, 0xf3, 0x5f, 0xae, 0xad, 0xb, 0xc1, 0x51, 0x8e, 0x17, 0x7, 0xe5, 0x7f, 0x91}} return a, nil } @@ -329,7 +330,7 @@ func _1597322655_add_invitation_admin_chat_fieldUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "1597322655_add_invitation_admin_chat_field.up.sql", size: 54, mode: os.FileMode(0644), modTime: time.Unix(1608547984, 0)} + info := bindataFileInfo{name: "1597322655_add_invitation_admin_chat_field.up.sql", size: 54, mode: os.FileMode(0644), modTime: time.Unix(1611588835, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xa9, 0x7a, 0xa0, 0xf2, 0xdb, 0x13, 0x91, 0x91, 0xa8, 0x34, 0x1a, 0xa1, 0x49, 0x68, 0xd5, 0xae, 0x2c, 0xd8, 0xd5, 0xea, 0x8f, 0x8c, 0xc7, 0x2, 0x4e, 0x58, 0x2c, 0x3a, 0x14, 0xd4, 0x4f, 0x2c}} return a, nil } @@ -349,7 +350,7 @@ func _1597757544_add_nicknameUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "1597757544_add_nickname.up.sql", size: 52, mode: os.FileMode(0644), modTime: time.Unix(1608547984, 0)} + info := bindataFileInfo{name: "1597757544_add_nickname.up.sql", size: 52, mode: os.FileMode(0644), modTime: time.Unix(1611588835, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xf4, 0xa2, 0x64, 0x50, 0xc5, 0x4, 0xb9, 0x8b, 0xd1, 0x18, 0x9b, 0xc3, 0x91, 0x36, 0x2a, 0x1f, 0xc3, 0x6c, 0x2d, 0x92, 0xf8, 0x5e, 0xff, 0xb1, 0x59, 0x61, 0x2, 0x1c, 0xe1, 0x85, 0x90, 0xa4}} return a, nil } @@ -369,7 +370,7 @@ func _1598955122_add_mentionsUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "1598955122_add_mentions.up.sql", size: 52, mode: os.FileMode(0644), modTime: time.Unix(1608547984, 0)} + info := bindataFileInfo{name: "1598955122_add_mentions.up.sql", size: 52, mode: os.FileMode(0644), modTime: time.Unix(1611588835, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x8d, 0x22, 0x17, 0x92, 0xd2, 0x11, 0x4e, 0x7, 0x93, 0x9a, 0x55, 0xfd, 0xb, 0x97, 0xc4, 0x63, 0x6a, 0x81, 0x97, 0xcd, 0xb2, 0xf8, 0x4b, 0x5f, 0x3c, 0xfa, 0x3a, 0x38, 0x53, 0x10, 0xed, 0x9d}} return a, nil } @@ -389,7 +390,7 @@ func _1599641390_add_emoji_reactions_indexUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "1599641390_add_emoji_reactions_index.up.sql", size: 126, mode: os.FileMode(0644), modTime: time.Unix(1608547984, 0)} + info := bindataFileInfo{name: "1599641390_add_emoji_reactions_index.up.sql", size: 126, mode: os.FileMode(0644), modTime: time.Unix(1611588835, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xf9, 0xd8, 0xdc, 0xa7, 0xb, 0x92, 0x7a, 0x61, 0x37, 0x24, 0x1c, 0x77, 0x5e, 0xe, 0x7e, 0xfc, 0x9f, 0x98, 0x7b, 0x65, 0xe7, 0xf9, 0x71, 0x57, 0x89, 0x2d, 0x90, 0x1b, 0xf6, 0x5e, 0x37, 0xe8}} return a, nil } @@ -409,7 +410,7 @@ func _1599720851_add_seen_index_remove_long_messagesUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "1599720851_add_seen_index_remove_long_messages.up.sql", size: 150, mode: os.FileMode(0644), modTime: time.Unix(1608547984, 0)} + info := bindataFileInfo{name: "1599720851_add_seen_index_remove_long_messages.up.sql", size: 150, mode: os.FileMode(0644), modTime: time.Unix(1611588835, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x24, 0x1c, 0xc4, 0x78, 0x91, 0xc7, 0xeb, 0xfe, 0xc8, 0xa0, 0xd8, 0x13, 0x27, 0x97, 0xc8, 0x96, 0x56, 0x97, 0x33, 0x2c, 0x1e, 0x16, 0x8a, 0xd3, 0x49, 0x99, 0x3, 0xe9, 0xbb, 0xc4, 0x5, 0x3c}} return a, nil } @@ -429,7 +430,7 @@ func _1603198582_add_profile_chat_fieldUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "1603198582_add_profile_chat_field.up.sql", size: 45, mode: os.FileMode(0644), modTime: time.Unix(1608547984, 0)} + info := bindataFileInfo{name: "1603198582_add_profile_chat_field.up.sql", size: 45, mode: os.FileMode(0644), modTime: time.Unix(1611588835, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xaa, 0xca, 0xe, 0x46, 0xa0, 0x9, 0x9d, 0x47, 0x57, 0xe9, 0xfb, 0x17, 0xeb, 0x9c, 0xf6, 0xb8, 0x1d, 0xe9, 0xd, 0x0, 0xd5, 0xe5, 0xd8, 0x9e, 0x60, 0xa, 0xbf, 0x32, 0x2c, 0x52, 0x7f, 0x6a}} return a, nil } @@ -449,7 +450,7 @@ func _1603816533_add_linksUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "1603816533_add_links.up.sql", size: 48, mode: os.FileMode(0644), modTime: time.Unix(1608547984, 0)} + info := bindataFileInfo{name: "1603816533_add_links.up.sql", size: 48, mode: os.FileMode(0644), modTime: time.Unix(1611588835, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xc9, 0x24, 0xd6, 0x1d, 0xa, 0x83, 0x1e, 0x4d, 0xf, 0xae, 0x4d, 0x8c, 0x51, 0x32, 0xa8, 0x37, 0xb0, 0x14, 0xfb, 0x32, 0x34, 0xc8, 0xc, 0x4e, 0x5b, 0xc5, 0x15, 0x65, 0x73, 0x0, 0x0, 0x1d}} return a, nil } @@ -469,7 +470,7 @@ func _1603888149_create_chat_identity_last_published_tableUpSql() (*asset, error return nil, err } - info := bindataFileInfo{name: "1603888149_create_chat_identity_last_published_table.up.sql", size: 407, mode: os.FileMode(0644), modTime: time.Unix(1608547984, 0)} + info := bindataFileInfo{name: "1603888149_create_chat_identity_last_published_table.up.sql", size: 407, mode: os.FileMode(0644), modTime: time.Unix(1611588835, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x7f, 0x9, 0xf, 0xfb, 0xdb, 0x3c, 0x86, 0x70, 0x82, 0xda, 0x10, 0x25, 0xe2, 0x4e, 0x40, 0x45, 0xab, 0x8b, 0x1c, 0x91, 0x7c, 0xf1, 0x70, 0x2e, 0x81, 0xf3, 0x71, 0x45, 0xda, 0xe2, 0xa4, 0x57}} return a, nil } @@ -489,7 +490,7 @@ func _1605075346_add_communitiesUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "1605075346_add_communities.up.sql", size: 6971, mode: os.FileMode(0644), modTime: time.Unix(1610097152, 0)} + info := bindataFileInfo{name: "1605075346_add_communities.up.sql", size: 6971, mode: os.FileMode(0644), modTime: time.Unix(1612251705, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x1f, 0x64, 0xea, 0xb4, 0xae, 0x9e, 0xdb, 0x9, 0x58, 0xb6, 0x5c, 0x7a, 0x50, 0xc5, 0xfe, 0x93, 0x5d, 0x36, 0x85, 0x5d, 0x6a, 0xba, 0xc9, 0x7e, 0x84, 0xd7, 0xbf, 0x2a, 0x53, 0xf3, 0x97, 0xf1}} return a, nil } @@ -509,7 +510,7 @@ func _1610117927_add_message_cacheUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "1610117927_add_message_cache.up.sql", size: 142, mode: os.FileMode(0644), modTime: time.Unix(1613123938, 0)} + info := bindataFileInfo{name: "1610117927_add_message_cache.up.sql", size: 142, mode: os.FileMode(0644), modTime: time.Unix(1612251705, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x34, 0xf1, 0xf0, 0x82, 0x79, 0x28, 0x19, 0xc2, 0x39, 0x6a, 0xa5, 0x96, 0x59, 0x23, 0xa0, 0xed, 0x60, 0x58, 0x86, 0x9, 0xb9, 0xad, 0xfb, 0xa, 0xe3, 0x47, 0x6e, 0xa1, 0x18, 0xe8, 0x39, 0x2c}} return a, nil } @@ -529,7 +530,7 @@ func _1610959908_add_dont_wrap_to_raw_messagesUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "1610959908_add_dont_wrap_to_raw_messages.up.sql", size: 83, mode: os.FileMode(0644), modTime: time.Unix(1613123938, 0)} + info := bindataFileInfo{name: "1610959908_add_dont_wrap_to_raw_messages.up.sql", size: 83, mode: os.FileMode(0644), modTime: time.Unix(1612251705, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x71, 0x2, 0x9a, 0xca, 0xd4, 0x38, 0x44, 0x30, 0x2b, 0xa8, 0x27, 0x32, 0x63, 0x53, 0x22, 0x60, 0x59, 0x84, 0x23, 0x96, 0x77, 0xf0, 0x56, 0xd7, 0x94, 0xe0, 0x95, 0x28, 0x6, 0x1d, 0x4e, 0xb1}} return a, nil } @@ -549,7 +550,7 @@ func _1610960912_add_send_on_personal_topicUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "1610960912_add_send_on_personal_topic.up.sql", size: 82, mode: os.FileMode(0644), modTime: time.Unix(1613123938, 0)} + info := bindataFileInfo{name: "1610960912_add_send_on_personal_topic.up.sql", size: 82, mode: os.FileMode(0644), modTime: time.Unix(1612251705, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x77, 0xac, 0x2f, 0xc4, 0xd, 0xa7, 0x1b, 0x37, 0x30, 0xc2, 0x68, 0xee, 0xde, 0x54, 0x5e, 0xbf, 0x3f, 0xa0, 0xd6, 0xc6, 0x9f, 0xd4, 0x34, 0x12, 0x76, 0x1e, 0x66, 0x4a, 0xfc, 0xf, 0xee, 0xc9}} return a, nil } @@ -569,11 +570,31 @@ func _1612870480_add_datasync_idUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "1612870480_add_datasync_id.up.sql", size: 111, mode: os.FileMode(0644), modTime: time.Unix(1613649828, 0)} + info := bindataFileInfo{name: "1612870480_add_datasync_id.up.sql", size: 111, mode: os.FileMode(0644), modTime: time.Unix(1614151150, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x34, 0x9a, 0xbc, 0xfa, 0xaa, 0x8c, 0x9c, 0x37, 0x67, 0x15, 0x9c, 0x7e, 0x78, 0x75, 0x66, 0x82, 0x18, 0x72, 0x10, 0xbc, 0xd4, 0xab, 0x44, 0xfe, 0x57, 0x85, 0x6d, 0x19, 0xf5, 0x96, 0x8a, 0xbe}} return a, nil } +var __1614152139_add_communities_request_to_joinUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8c\x90\x41\x6f\xe2\x30\x14\x84\xef\xf9\x15\x4f\x9c\x40\xca\x61\xef\x9c\x9c\xec\x8b\x88\xd6\xd8\xc8\x98\xad\x38\x59\xa9\xe3\xaa\x2e\x60\xb7\xb1\xa9\xca\xbf\xaf\x12\x5a\x20\x40\xa1\x47\xfb\xcd\x8c\x66\xbe\x5c\x20\x91\x08\x92\x64\x14\x41\xfb\xcd\x66\xeb\x6c\xb4\x26\xa8\xc6\xbc\x6d\x4d\x88\x41\x45\xaf\x5e\xbc\x75\x00\xc3\x04\xc0\xd6\x90\x51\x9e\x01\xe3\x12\xd8\x82\xd2\x34\x01\x78\xdd\x3e\xae\xad\x56\x2b\xb3\x83\xff\x44\xe4\x13\x22\x7a\x67\xbd\xf6\x7a\x05\x25\x93\xbd\x5f\xe3\x82\x72\xd5\xc6\x5c\x58\xe0\x2f\x16\x64\x41\x25\x0c\x06\x9d\xfb\xb9\x8a\xca\xd6\x77\x65\x5f\xcd\x77\xea\x5a\xc3\x10\xab\x68\x7a\x15\x0e\xf6\x3f\xed\x7d\x26\xca\x29\x11\x4b\xf8\x87\x4b\x18\xda\x7a\x04\x9c\x41\xce\x59\x41\xcb\x5c\x82\xc0\x19\x25\x39\x26\xa3\x71\x92\x24\x3d\x5c\xed\x86\x77\xd3\xd8\x27\xab\xab\x68\xbd\x53\x8d\xd1\xbe\xa9\x43\x47\xea\x0e\x95\xab\xdb\xdb\xc3\x3e\xd0\xd4\x90\x71\x4e\x91\xb0\xcb\xca\x05\xa1\x73\x3c\x95\xaa\x2a\xde\x18\x77\xc9\xbf\x7f\x3f\x5b\x10\x1b\x6b\xc2\x0d\xb9\x33\x1f\xb1\x93\xed\x7e\x0b\xf4\x48\xe2\x67\xb0\x25\x9b\xa3\x90\x6d\x22\xbf\x81\xf5\x98\x94\x76\x00\xd3\x03\x82\xf4\x14\x46\xba\xdf\x3c\x82\x39\x52\xcc\x25\xd8\xfa\x5b\x7e\x8c\x6e\x2d\xa7\xaf\xce\x76\xf6\x01\x85\xe0\x53\xd0\xde\xc5\x4a\xc7\x00\x0f\x13\x14\xd8\xd3\x8c\x93\xcf\x00\x00\x00\xff\xff\xbf\xa2\xec\x31\x3f\x03\x00\x00") + +func _1614152139_add_communities_request_to_joinUpSqlBytes() ([]byte, error) { + return bindataRead( + __1614152139_add_communities_request_to_joinUpSql, + "1614152139_add_communities_request_to_join.up.sql", + ) +} + +func _1614152139_add_communities_request_to_joinUpSql() (*asset, error) { + bytes, err := _1614152139_add_communities_request_to_joinUpSqlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "1614152139_add_communities_request_to_join.up.sql", size: 831, mode: os.FileMode(0644), modTime: time.Unix(1614152078, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x11, 0x3, 0x26, 0xf9, 0x29, 0x50, 0x4f, 0xcd, 0x46, 0xe5, 0xb1, 0x6b, 0xb9, 0x2, 0x40, 0xb1, 0xdf, 0x4a, 0x4c, 0x7a, 0xda, 0x3, 0x35, 0xcd, 0x2d, 0xcc, 0x80, 0x7d, 0x57, 0x5f, 0x3, 0x5c}} + return a, nil +} + var _readmeMd = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x54\x91\xc1\xce\xd3\x30\x10\x84\xef\x7e\x8a\x91\x7a\x01\xa9\x2a\x8f\xc0\x0d\x71\x82\x03\x48\x1c\xc9\x36\x9e\x36\x96\x1c\x6f\xf0\xae\x93\xe6\xed\x91\xa3\xc2\xdf\xff\x66\xed\xd8\x33\xdf\x78\x4f\xa7\x13\xbe\xea\x06\x57\x6c\x35\x39\x31\xa7\x7b\x15\x4f\x5a\xec\x73\x08\xbf\x08\x2d\x79\x7f\x4a\x43\x5b\x86\x17\xfd\x8c\x21\xea\x56\x5e\x47\x90\x4a\x14\x75\x48\xde\x64\x37\x2c\x6a\x96\xae\x99\x48\x05\xf6\x27\x77\x13\xad\x08\xae\x8a\x51\xe7\x25\xf3\xf1\xa9\x9f\xf9\x58\x58\x2c\xad\xbc\xe0\x8b\x56\xf0\x21\x5d\xeb\x4c\x95\xb3\xae\x84\x60\xd4\xdc\xe6\x82\x5d\x1b\x36\x6d\x39\x62\x92\xf5\xb8\x11\xdb\x92\xd3\x28\xce\xe0\x13\xe1\x72\xcd\x3c\x63\xd4\x65\x87\xae\xac\xe8\xc3\x28\x2e\x67\x44\x66\x3a\x21\x25\xa2\x72\xac\x14\x67\xbc\x84\x9f\x53\x32\x8c\x52\x70\x25\x56\xd6\xfd\x8d\x05\x37\xad\x30\x9d\x9f\xa6\x86\x0f\xcd\x58\x7f\xcf\x34\x93\x3b\xed\x90\x9f\xa4\x1f\xcf\x30\x85\x4d\x07\x58\xaf\x7f\x25\xc4\x9d\xf3\x72\x64\x84\xd0\x7f\xf9\x9b\x3a\x2d\x84\xef\x85\x48\x66\x8d\xd8\x88\x9b\x8c\x8c\x98\x5b\xf6\x74\x14\x4e\x33\x0d\xc9\xe0\x93\x38\xda\x12\xc5\x69\xbd\xe4\xf0\x2e\x7a\x78\x07\x1c\xfe\x13\x9f\x91\x29\x31\x95\x7b\x7f\x62\x59\x37\xb4\xe5\x5e\x25\xfe\x33\xee\xd5\x53\x71\xd6\xda\x3a\xd8\xcb\xde\x2e\xf8\xa1\x90\x55\x53\x0c\xc7\xaa\x0d\xe9\x76\x14\x29\x1c\x7b\x68\xdd\x2f\xe1\x6f\x00\x00\x00\xff\xff\x3c\x0a\xc2\xfe\x2a\x02\x00\x00") func readmeMdBytes() ([]byte, error) { @@ -589,7 +610,7 @@ func readmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "README.md", size: 554, mode: os.FileMode(0644), modTime: time.Unix(1610097152, 0)} + info := bindataFileInfo{name: "README.md", size: 554, mode: os.FileMode(0644), modTime: time.Unix(1612251705, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x1c, 0x6e, 0xfb, 0xcc, 0x81, 0x94, 0x4d, 0x8c, 0xa0, 0x3b, 0x5, 0xb0, 0x18, 0xd6, 0xbb, 0xb3, 0x79, 0xc8, 0x8f, 0xff, 0xc1, 0x10, 0xf9, 0xf, 0x20, 0x1b, 0x4a, 0x74, 0x96, 0x42, 0xd7, 0xa8}} return a, nil } @@ -609,7 +630,7 @@ func docGo() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "doc.go", size: 850, mode: os.FileMode(0644), modTime: time.Unix(1608547984, 0)} + info := bindataFileInfo{name: "doc.go", size: 850, mode: os.FileMode(0644), modTime: time.Unix(1611588719, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xa0, 0xcc, 0x41, 0xe1, 0x61, 0x12, 0x97, 0xe, 0x36, 0x8c, 0xa7, 0x9e, 0xe0, 0x6e, 0x59, 0x9e, 0xee, 0xd5, 0x4a, 0xcf, 0x1e, 0x60, 0xd6, 0xc3, 0x3a, 0xc9, 0x6c, 0xf2, 0x86, 0x5a, 0xb4, 0x1e}} return a, nil } @@ -705,36 +726,60 @@ func AssetNames() []string { // _bindata is a table, holding each asset generator, mapped to its name. var _bindata = map[string]func() (*asset, error){ - "000001_init.down.db.sql": _000001_initDownDbSql, - "000001_init.up.db.sql": _000001_initUpDbSql, - "000002_add_last_ens_clock_value.up.sql": _000002_add_last_ens_clock_valueUpSql, - "1586358095_add_replace.up.sql": _1586358095_add_replaceUpSql, - "1588665364_add_image_data.up.sql": _1588665364_add_image_dataUpSql, - "1589365189_add_pow_target.up.sql": _1589365189_add_pow_targetUpSql, - "1591277220_add_index_messages.up.sql": _1591277220_add_index_messagesUpSql, - "1593087212_add_mute_chat_and_raw_message_fields.up.sql": _1593087212_add_mute_chat_and_raw_message_fieldsUpSql, - "1595862781_add_audio_data.up.sql": _1595862781_add_audio_dataUpSql, - "1595865249_create_emoji_reactions_table.up.sql": _1595865249_create_emoji_reactions_tableUpSql, - "1596805115_create_group_chat_invitations_table.up.sql": _1596805115_create_group_chat_invitations_tableUpSql, - "1597322655_add_invitation_admin_chat_field.up.sql": _1597322655_add_invitation_admin_chat_fieldUpSql, - "1597757544_add_nickname.up.sql": _1597757544_add_nicknameUpSql, - "1598955122_add_mentions.up.sql": _1598955122_add_mentionsUpSql, - "1599641390_add_emoji_reactions_index.up.sql": _1599641390_add_emoji_reactions_indexUpSql, - "1599720851_add_seen_index_remove_long_messages.up.sql": _1599720851_add_seen_index_remove_long_messagesUpSql, - "1603198582_add_profile_chat_field.up.sql": _1603198582_add_profile_chat_fieldUpSql, - "1603816533_add_links.up.sql": _1603816533_add_linksUpSql, - "1603888149_create_chat_identity_last_published_table.up.sql": _1603888149_create_chat_identity_last_published_tableUpSql, - "1605075346_add_communities.up.sql": _1605075346_add_communitiesUpSql, - "1610117927_add_message_cache.up.sql": _1610117927_add_message_cacheUpSql, - "1610959908_add_dont_wrap_to_raw_messages.up.sql": _1610959908_add_dont_wrap_to_raw_messagesUpSql, - "1610960912_add_send_on_personal_topic.up.sql": _1610960912_add_send_on_personal_topicUpSql, - "1612870480_add_datasync_id.up.sql": _1612870480_add_datasync_idUpSql, - "README.md": readmeMd, - "doc.go": docGo, -} + "000001_init.down.db.sql": _000001_initDownDbSql, -// AssetDebug is true if the assets were built with the debug flag enabled. -const AssetDebug = false + "000001_init.up.db.sql": _000001_initUpDbSql, + + "000002_add_last_ens_clock_value.up.sql": _000002_add_last_ens_clock_valueUpSql, + + "1586358095_add_replace.up.sql": _1586358095_add_replaceUpSql, + + "1588665364_add_image_data.up.sql": _1588665364_add_image_dataUpSql, + + "1589365189_add_pow_target.up.sql": _1589365189_add_pow_targetUpSql, + + "1591277220_add_index_messages.up.sql": _1591277220_add_index_messagesUpSql, + + "1593087212_add_mute_chat_and_raw_message_fields.up.sql": _1593087212_add_mute_chat_and_raw_message_fieldsUpSql, + + "1595862781_add_audio_data.up.sql": _1595862781_add_audio_dataUpSql, + + "1595865249_create_emoji_reactions_table.up.sql": _1595865249_create_emoji_reactions_tableUpSql, + + "1596805115_create_group_chat_invitations_table.up.sql": _1596805115_create_group_chat_invitations_tableUpSql, + + "1597322655_add_invitation_admin_chat_field.up.sql": _1597322655_add_invitation_admin_chat_fieldUpSql, + + "1597757544_add_nickname.up.sql": _1597757544_add_nicknameUpSql, + + "1598955122_add_mentions.up.sql": _1598955122_add_mentionsUpSql, + + "1599641390_add_emoji_reactions_index.up.sql": _1599641390_add_emoji_reactions_indexUpSql, + + "1599720851_add_seen_index_remove_long_messages.up.sql": _1599720851_add_seen_index_remove_long_messagesUpSql, + + "1603198582_add_profile_chat_field.up.sql": _1603198582_add_profile_chat_fieldUpSql, + + "1603816533_add_links.up.sql": _1603816533_add_linksUpSql, + + "1603888149_create_chat_identity_last_published_table.up.sql": _1603888149_create_chat_identity_last_published_tableUpSql, + + "1605075346_add_communities.up.sql": _1605075346_add_communitiesUpSql, + + "1610117927_add_message_cache.up.sql": _1610117927_add_message_cacheUpSql, + + "1610959908_add_dont_wrap_to_raw_messages.up.sql": _1610959908_add_dont_wrap_to_raw_messagesUpSql, + + "1610960912_add_send_on_personal_topic.up.sql": _1610960912_add_send_on_personal_topicUpSql, + + "1612870480_add_datasync_id.up.sql": _1612870480_add_datasync_idUpSql, + + "1614152139_add_communities_request_to_join.up.sql": _1614152139_add_communities_request_to_joinUpSql, + + "README.md": readmeMd, + + "doc.go": docGo, +} // AssetDir returns the file names below a certain // directory embedded in the file by go-bindata. @@ -777,32 +822,33 @@ type bintree struct { } var _bintree = &bintree{nil, map[string]*bintree{ - "000001_init.down.db.sql": {_000001_initDownDbSql, map[string]*bintree{}}, - "000001_init.up.db.sql": {_000001_initUpDbSql, map[string]*bintree{}}, - "000002_add_last_ens_clock_value.up.sql": {_000002_add_last_ens_clock_valueUpSql, map[string]*bintree{}}, - "1586358095_add_replace.up.sql": {_1586358095_add_replaceUpSql, map[string]*bintree{}}, - "1588665364_add_image_data.up.sql": {_1588665364_add_image_dataUpSql, map[string]*bintree{}}, - "1589365189_add_pow_target.up.sql": {_1589365189_add_pow_targetUpSql, map[string]*bintree{}}, - "1591277220_add_index_messages.up.sql": {_1591277220_add_index_messagesUpSql, map[string]*bintree{}}, - "1593087212_add_mute_chat_and_raw_message_fields.up.sql": {_1593087212_add_mute_chat_and_raw_message_fieldsUpSql, map[string]*bintree{}}, - "1595862781_add_audio_data.up.sql": {_1595862781_add_audio_dataUpSql, map[string]*bintree{}}, - "1595865249_create_emoji_reactions_table.up.sql": {_1595865249_create_emoji_reactions_tableUpSql, map[string]*bintree{}}, - "1596805115_create_group_chat_invitations_table.up.sql": {_1596805115_create_group_chat_invitations_tableUpSql, map[string]*bintree{}}, - "1597322655_add_invitation_admin_chat_field.up.sql": {_1597322655_add_invitation_admin_chat_fieldUpSql, map[string]*bintree{}}, - "1597757544_add_nickname.up.sql": {_1597757544_add_nicknameUpSql, map[string]*bintree{}}, - "1598955122_add_mentions.up.sql": {_1598955122_add_mentionsUpSql, map[string]*bintree{}}, - "1599641390_add_emoji_reactions_index.up.sql": {_1599641390_add_emoji_reactions_indexUpSql, map[string]*bintree{}}, - "1599720851_add_seen_index_remove_long_messages.up.sql": {_1599720851_add_seen_index_remove_long_messagesUpSql, map[string]*bintree{}}, - "1603198582_add_profile_chat_field.up.sql": {_1603198582_add_profile_chat_fieldUpSql, map[string]*bintree{}}, - "1603816533_add_links.up.sql": {_1603816533_add_linksUpSql, map[string]*bintree{}}, - "1603888149_create_chat_identity_last_published_table.up.sql": {_1603888149_create_chat_identity_last_published_tableUpSql, map[string]*bintree{}}, - "1605075346_add_communities.up.sql": {_1605075346_add_communitiesUpSql, map[string]*bintree{}}, - "1610117927_add_message_cache.up.sql": {_1610117927_add_message_cacheUpSql, map[string]*bintree{}}, - "1610959908_add_dont_wrap_to_raw_messages.up.sql": {_1610959908_add_dont_wrap_to_raw_messagesUpSql, map[string]*bintree{}}, - "1610960912_add_send_on_personal_topic.up.sql": {_1610960912_add_send_on_personal_topicUpSql, map[string]*bintree{}}, - "1612870480_add_datasync_id.up.sql": {_1612870480_add_datasync_idUpSql, map[string]*bintree{}}, - "README.md": {readmeMd, map[string]*bintree{}}, - "doc.go": {docGo, map[string]*bintree{}}, + "000001_init.down.db.sql": &bintree{_000001_initDownDbSql, map[string]*bintree{}}, + "000001_init.up.db.sql": &bintree{_000001_initUpDbSql, map[string]*bintree{}}, + "000002_add_last_ens_clock_value.up.sql": &bintree{_000002_add_last_ens_clock_valueUpSql, map[string]*bintree{}}, + "1586358095_add_replace.up.sql": &bintree{_1586358095_add_replaceUpSql, map[string]*bintree{}}, + "1588665364_add_image_data.up.sql": &bintree{_1588665364_add_image_dataUpSql, map[string]*bintree{}}, + "1589365189_add_pow_target.up.sql": &bintree{_1589365189_add_pow_targetUpSql, map[string]*bintree{}}, + "1591277220_add_index_messages.up.sql": &bintree{_1591277220_add_index_messagesUpSql, map[string]*bintree{}}, + "1593087212_add_mute_chat_and_raw_message_fields.up.sql": &bintree{_1593087212_add_mute_chat_and_raw_message_fieldsUpSql, map[string]*bintree{}}, + "1595862781_add_audio_data.up.sql": &bintree{_1595862781_add_audio_dataUpSql, map[string]*bintree{}}, + "1595865249_create_emoji_reactions_table.up.sql": &bintree{_1595865249_create_emoji_reactions_tableUpSql, map[string]*bintree{}}, + "1596805115_create_group_chat_invitations_table.up.sql": &bintree{_1596805115_create_group_chat_invitations_tableUpSql, map[string]*bintree{}}, + "1597322655_add_invitation_admin_chat_field.up.sql": &bintree{_1597322655_add_invitation_admin_chat_fieldUpSql, map[string]*bintree{}}, + "1597757544_add_nickname.up.sql": &bintree{_1597757544_add_nicknameUpSql, map[string]*bintree{}}, + "1598955122_add_mentions.up.sql": &bintree{_1598955122_add_mentionsUpSql, map[string]*bintree{}}, + "1599641390_add_emoji_reactions_index.up.sql": &bintree{_1599641390_add_emoji_reactions_indexUpSql, map[string]*bintree{}}, + "1599720851_add_seen_index_remove_long_messages.up.sql": &bintree{_1599720851_add_seen_index_remove_long_messagesUpSql, map[string]*bintree{}}, + "1603198582_add_profile_chat_field.up.sql": &bintree{_1603198582_add_profile_chat_fieldUpSql, map[string]*bintree{}}, + "1603816533_add_links.up.sql": &bintree{_1603816533_add_linksUpSql, map[string]*bintree{}}, + "1603888149_create_chat_identity_last_published_table.up.sql": &bintree{_1603888149_create_chat_identity_last_published_tableUpSql, map[string]*bintree{}}, + "1605075346_add_communities.up.sql": &bintree{_1605075346_add_communitiesUpSql, map[string]*bintree{}}, + "1610117927_add_message_cache.up.sql": &bintree{_1610117927_add_message_cacheUpSql, map[string]*bintree{}}, + "1610959908_add_dont_wrap_to_raw_messages.up.sql": &bintree{_1610959908_add_dont_wrap_to_raw_messagesUpSql, map[string]*bintree{}}, + "1610960912_add_send_on_personal_topic.up.sql": &bintree{_1610960912_add_send_on_personal_topicUpSql, map[string]*bintree{}}, + "1612870480_add_datasync_id.up.sql": &bintree{_1612870480_add_datasync_idUpSql, map[string]*bintree{}}, + "1614152139_add_communities_request_to_join.up.sql": &bintree{_1614152139_add_communities_request_to_joinUpSql, map[string]*bintree{}}, + "README.md": &bintree{readmeMd, map[string]*bintree{}}, + "doc.go": &bintree{docGo, map[string]*bintree{}}, }} // RestoreAsset restores an asset under the given directory. diff --git a/protocol/migrations/sqlite/1614152139_add_communities_request_to_join.up.sql b/protocol/migrations/sqlite/1614152139_add_communities_request_to_join.up.sql new file mode 100644 index 000000000..890d0e8f1 --- /dev/null +++ b/protocol/migrations/sqlite/1614152139_add_communities_request_to_join.up.sql @@ -0,0 +1,24 @@ +CREATE TABLE communities_requests_to_join ( + id BLOB NOT NULL, + public_key VARCHAR NOT NULL, + clock INT NOT NULL, + ens_name VARCHAR NOT NULL DEFAULT "", + chat_id VARCHAR NOT NULL DEFAULT "", + community_id BLOB NOT NULL, + state INT NOT NULL DEFAULT 0, + PRIMARY KEY (id) ON CONFLICT REPLACE +); + + +CREATE TABLE ens_verification_records ( + public_key VARCHAR NOT NULL, + name VARCHAR NOT NULL, + verified BOOLEAN NOT NULL DEFAULT FALSE, + verified_at INT NOT NULL DEFAULT 0, + clock INT NOT NULL DEFAULT 0, + verification_retries INT NOT NULL DEFAULT 0, + next_retry INT NOT NULL DEFAULT 0, + PRIMARY KEY (public_key) ON CONFLICT REPLACE +); + +INSERT INTO ens_verification_records (public_key, name, verified, verified_at, clock) SELECT id, name, ens_verified, ens_verified_at, ens_verified_at FROM contacts WHERE ens_verified; diff --git a/protocol/persistence.go b/protocol/persistence.go index c8bdf077c..831e25f47 100644 --- a/protocol/persistence.go +++ b/protocol/persistence.go @@ -409,19 +409,17 @@ func (db sqlitePersistence) Contacts() ([]*Contact, error) { SELECT c.id, c.address, - c.name, + v.name, + v.verified, c.alias, c.identicon, c.last_updated, c.system_tags, c.device_info, - c.ens_verified, - c.ens_verified_at, - c.tribute_to_talk, c.local_nickname, i.image_type, i.payload - FROM contacts c LEFT JOIN chat_identity_contacts i ON c.id = i.contact_id + FROM contacts c LEFT JOIN chat_identity_contacts i ON c.id = i.contact_id LEFT JOIN ens_verification_records v ON c.id = v.public_key `) if err != nil { return nil, err @@ -436,6 +434,8 @@ func (db sqlitePersistence) Contacts() ([]*Contact, error) { encodedSystemTags []byte nickname sql.NullString imageType sql.NullString + ensName sql.NullString + ensVerified sql.NullBool imagePayload []byte ) @@ -444,15 +444,13 @@ func (db sqlitePersistence) Contacts() ([]*Contact, error) { err := rows.Scan( &contact.ID, &contact.Address, - &contact.Name, + &ensName, + &ensVerified, &contact.Alias, &contact.Identicon, &contact.LastUpdated, &encodedSystemTags, &encodedDeviceInfo, - &contact.ENSVerified, - &contact.ENSVerifiedAt, - &contact.TributeToTalk, &nickname, &imageType, &imagePayload, @@ -465,6 +463,14 @@ func (db sqlitePersistence) Contacts() ([]*Contact, error) { contact.LocalNickname = nickname.String } + if ensName.Valid { + contact.Name = ensName.String + } + + if ensVerified.Valid { + contact.ENSVerified = ensVerified.Bool + } + if encodedDeviceInfo != nil { // Restore device info deviceInfoDecoder := gob.NewDecoder(bytes.NewBuffer(encodedDeviceInfo)) @@ -798,6 +804,9 @@ func (db sqlitePersistence) SaveContact(contact *Contact, tx *sql.Tx) (err error } // Insert record + // NOTE: tribute_to_talk is not used anymore, but it's not nullable + // Removing it requires copying over the table which might be expensive + // when there are many contacts, so best avoiding it stmt, err := tx.Prepare(` INSERT INTO contacts( id, @@ -808,12 +817,10 @@ func (db sqlitePersistence) SaveContact(contact *Contact, tx *sql.Tx) (err error last_updated, system_tags, device_info, - ens_verified, - ens_verified_at, - tribute_to_talk, local_nickname, - photo - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?, ?) + photo, + tribute_to_talk + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?,?, ?, "") `) if err != nil { return @@ -829,9 +836,6 @@ func (db sqlitePersistence) SaveContact(contact *Contact, tx *sql.Tx) (err error contact.LastUpdated, encodedSystemTags.Bytes(), encodedDeviceInfo.Bytes(), - contact.ENSVerified, - contact.ENSVerifiedAt, - contact.TributeToTalk, contact.LocalNickname, // Photo is not used anymore but constrained to be NOT NULL // we set it to blank for now to avoid a migration of the table diff --git a/protocol/persistence_test.go b/protocol/persistence_test.go index 4fbf0fd72..f8a92eb2f 100644 --- a/protocol/persistence_test.go +++ b/protocol/persistence_test.go @@ -606,12 +606,12 @@ func TestSaveChat(t *testing.T) { chat := CreatePublicChat("test-chat", &testTimeSource{}) chat.LastMessage = &common.Message{} - err = p.SaveChat(chat) + err = p.SaveChat(*chat) require.NoError(t, err) retrievedChat, err := p.Chat(chat.ID) require.NoError(t, err) - require.Equal(t, &chat, retrievedChat) + require.Equal(t, chat, retrievedChat) } func TestSaveMentions(t *testing.T) { @@ -821,7 +821,7 @@ func TestDeactivatePublicChat(t *testing.T) { publicChat.LastMessage = &lastMessage publicChat.UnviewedMessagesCount = 1 - err = p.DeactivateChat(&publicChat, currentClockValue) + err = p.DeactivateChat(publicChat, currentClockValue) // It does not set deleted at for a public chat require.NoError(t, err) @@ -890,7 +890,7 @@ func TestDeactivateOneToOneChat(t *testing.T) { chat.LastMessage = &lastMessage chat.UnviewedMessagesCount = 1 - err = p.DeactivateChat(&chat, currentClockValue) + err = p.DeactivateChat(chat, currentClockValue) // It does set deleted at for a public chat require.NoError(t, err) diff --git a/protocol/protobuf/application_metadata_message.pb.go b/protocol/protobuf/application_metadata_message.pb.go index 4d4a0cf70..67cb3bc32 100644 --- a/protocol/protobuf/application_metadata_message.pb.go +++ b/protocol/protobuf/application_metadata_message.pb.go @@ -50,6 +50,7 @@ const ( ApplicationMetadataMessage_CHAT_IDENTITY ApplicationMetadataMessage_Type = 24 ApplicationMetadataMessage_COMMUNITY_DESCRIPTION ApplicationMetadataMessage_Type = 25 ApplicationMetadataMessage_COMMUNITY_INVITATION ApplicationMetadataMessage_Type = 26 + ApplicationMetadataMessage_COMMUNITY_REQUEST_TO_JOIN ApplicationMetadataMessage_Type = 27 ) var ApplicationMetadataMessage_Type_name = map[int32]string{ @@ -80,6 +81,7 @@ var ApplicationMetadataMessage_Type_name = map[int32]string{ 24: "CHAT_IDENTITY", 25: "COMMUNITY_DESCRIPTION", 26: "COMMUNITY_INVITATION", + 27: "COMMUNITY_REQUEST_TO_JOIN", } var ApplicationMetadataMessage_Type_value = map[string]int32{ @@ -110,6 +112,7 @@ var ApplicationMetadataMessage_Type_value = map[string]int32{ "CHAT_IDENTITY": 24, "COMMUNITY_DESCRIPTION": 25, "COMMUNITY_INVITATION": 26, + "COMMUNITY_REQUEST_TO_JOIN": 27, } func (x ApplicationMetadataMessage_Type) String() string { @@ -188,38 +191,39 @@ func init() { } var fileDescriptor_ad09a6406fcf24c7 = []byte{ - // 527 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x93, 0x5d, 0x53, 0xd3, 0x4e, - 0x14, 0xc6, 0xff, 0x05, 0xfe, 0x14, 0x0e, 0x2f, 0x2e, 0x07, 0x90, 0x00, 0xf2, 0x62, 0x75, 0x14, - 0x75, 0xa6, 0x17, 0x7a, 0xed, 0xc5, 0xb2, 0x39, 0xd0, 0xd5, 0x66, 0x13, 0x76, 0x37, 0x38, 0xbd, - 0xda, 0x09, 0x12, 0x99, 0xce, 0x00, 0xcd, 0xd0, 0x70, 0xd1, 0x4f, 0xe3, 0xa7, 0xf0, 0xfb, 0x39, - 0x49, 0x5b, 0x5b, 0x6c, 0x91, 0xab, 0xcc, 0x3e, 0xcf, 0xef, 0x9c, 0x33, 0xe7, 0xd9, 0x2c, 0xd4, - 0x92, 0x2c, 0xbb, 0x6e, 0x7f, 0x4f, 0xf2, 0x76, 0xe7, 0xd6, 0xdd, 0xa4, 0x79, 0x72, 0x99, 0xe4, - 0x89, 0xbb, 0x49, 0xbb, 0xdd, 0xe4, 0x2a, 0xad, 0x67, 0x77, 0x9d, 0xbc, 0x83, 0x0b, 0xe5, 0xe7, - 0xe2, 0xfe, 0x47, 0xed, 0x57, 0x15, 0x76, 0xf8, 0xa8, 0x20, 0x18, 0xf0, 0x41, 0x1f, 0xc7, 0x17, - 0xb0, 0xd8, 0x6d, 0x5f, 0xdd, 0x26, 0xf9, 0xfd, 0x5d, 0xea, 0x55, 0x0e, 0x2b, 0x47, 0xcb, 0x7a, - 0x24, 0xa0, 0x07, 0xd5, 0x2c, 0xe9, 0x5d, 0x77, 0x92, 0x4b, 0x6f, 0xa6, 0xf4, 0x86, 0x47, 0xfc, - 0x0c, 0x73, 0x79, 0x2f, 0x4b, 0xbd, 0xd9, 0xc3, 0xca, 0xd1, 0xea, 0xc7, 0x77, 0xf5, 0xe1, 0xbc, - 0xfa, 0xe3, 0xb3, 0xea, 0xb6, 0x97, 0xa5, 0xba, 0x2c, 0xab, 0xfd, 0x9c, 0x87, 0xb9, 0xe2, 0x88, - 0x4b, 0x50, 0x8d, 0xd5, 0x57, 0x15, 0x7e, 0x53, 0xec, 0x3f, 0x64, 0xb0, 0x2c, 0x1a, 0xdc, 0xba, - 0x80, 0x8c, 0xe1, 0xa7, 0xc4, 0x2a, 0x88, 0xb0, 0x2a, 0x42, 0x65, 0xb9, 0xb0, 0x2e, 0x8e, 0x7c, - 0x6e, 0x89, 0xcd, 0xe0, 0x1e, 0x6c, 0x07, 0x14, 0x1c, 0x93, 0x36, 0x0d, 0x19, 0x0d, 0xe4, 0x3f, - 0x25, 0xb3, 0xb8, 0x09, 0x6b, 0x11, 0x97, 0xda, 0x49, 0x65, 0x2c, 0x6f, 0x36, 0xb9, 0x95, 0xa1, - 0x62, 0x73, 0x85, 0x6c, 0x5a, 0x4a, 0x3c, 0x94, 0xff, 0xc7, 0x57, 0x70, 0xa0, 0xe9, 0x2c, 0x26, - 0x63, 0x1d, 0xf7, 0x7d, 0x4d, 0xc6, 0xb8, 0x93, 0x50, 0x3b, 0xab, 0xb9, 0x32, 0x5c, 0x94, 0xd0, - 0x3c, 0xbe, 0x87, 0x37, 0x5c, 0x08, 0x8a, 0xac, 0x7b, 0x8a, 0xad, 0xe2, 0x07, 0x78, 0xeb, 0x93, - 0x68, 0x4a, 0x45, 0x4f, 0xc2, 0x0b, 0xb8, 0x05, 0xeb, 0x43, 0x68, 0xdc, 0x58, 0xc4, 0x0d, 0x60, - 0x86, 0x94, 0xff, 0x40, 0x05, 0x3c, 0x80, 0xdd, 0xbf, 0x7b, 0x8f, 0x03, 0x4b, 0x45, 0x34, 0x13, - 0x4b, 0xba, 0x41, 0x80, 0x6c, 0x79, 0xba, 0xcd, 0x85, 0x08, 0x63, 0x65, 0xd9, 0x0a, 0xbe, 0x84, - 0xbd, 0x49, 0x3b, 0x8a, 0x8f, 0x9b, 0x52, 0xb8, 0xe2, 0x5e, 0xd8, 0x2a, 0xee, 0xc3, 0xce, 0xf0, - 0x3e, 0x44, 0xe8, 0x93, 0xe3, 0xfe, 0x39, 0x69, 0x2b, 0x0d, 0x05, 0xa4, 0x2c, 0x7b, 0x86, 0x35, - 0xd8, 0x8f, 0x62, 0xd3, 0x70, 0x2a, 0xb4, 0xf2, 0x44, 0x8a, 0x7e, 0x0b, 0x4d, 0xa7, 0xd2, 0x58, - 0xdd, 0x8f, 0x9c, 0x15, 0x09, 0xfd, 0x9b, 0x71, 0x9a, 0x4c, 0x14, 0x2a, 0x43, 0x6c, 0x0d, 0x77, - 0x61, 0x6b, 0x12, 0x3e, 0x8b, 0x49, 0xb7, 0x18, 0xe2, 0x6b, 0x38, 0x7c, 0xc4, 0x1c, 0xb5, 0x58, - 0x2f, 0xb6, 0x9e, 0x36, 0xaf, 0xcc, 0x8f, 0x6d, 0x14, 0x2b, 0x4d, 0xb3, 0x07, 0xe5, 0x9b, 0xc5, - 0x2f, 0x48, 0x41, 0xf8, 0x45, 0x3a, 0x4d, 0x83, 0x9c, 0x9f, 0xe3, 0x36, 0x6c, 0x9e, 0xea, 0x30, - 0x8e, 0xca, 0x58, 0x9c, 0x54, 0xe7, 0xd2, 0xf6, 0xb7, 0xdb, 0xc2, 0x35, 0x58, 0xe9, 0x8b, 0x3e, - 0x29, 0x2b, 0x6d, 0x8b, 0x79, 0x05, 0x2d, 0xc2, 0x20, 0x88, 0x95, 0xb4, 0x2d, 0xe7, 0x93, 0x11, - 0x5a, 0x46, 0x25, 0xbd, 0x8d, 0x1e, 0x6c, 0x8c, 0xac, 0xb1, 0x3e, 0x3b, 0x17, 0xf3, 0xe5, 0x8b, - 0xfa, 0xf4, 0x3b, 0x00, 0x00, 0xff, 0xff, 0x42, 0x01, 0x7e, 0x48, 0xee, 0x03, 0x00, 0x00, + // 539 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x93, 0x4d, 0x53, 0xdb, 0x3e, + 0x10, 0xc6, 0xff, 0x01, 0xfe, 0x04, 0x96, 0x97, 0x8a, 0x05, 0x8a, 0x81, 0xf2, 0xd2, 0xb4, 0xd3, + 0xd2, 0x76, 0x26, 0x87, 0xf6, 0xdc, 0x83, 0x90, 0x17, 0x10, 0x8d, 0x25, 0x23, 0xc9, 0x74, 0x72, + 0xd2, 0x98, 0xe2, 0x32, 0xcc, 0x00, 0xf1, 0x80, 0x39, 0xf0, 0xe5, 0xfa, 0x29, 0xfa, 0x81, 0x3a, + 0x76, 0x12, 0x02, 0x4d, 0x28, 0x27, 0x8f, 0x9e, 0xe7, 0xb7, 0x2b, 0xef, 0xb3, 0x36, 0x34, 0xd2, + 0x3c, 0xbf, 0x38, 0xff, 0x91, 0x16, 0xe7, 0x9d, 0x2b, 0x7f, 0x99, 0x15, 0xe9, 0x69, 0x5a, 0xa4, + 0xfe, 0x32, 0xbb, 0xb9, 0x49, 0xcf, 0xb2, 0x66, 0x7e, 0xdd, 0x29, 0x3a, 0x38, 0x55, 0x3d, 0x4e, + 0x6e, 0x7f, 0x36, 0x7e, 0xd7, 0x61, 0x8d, 0x0f, 0x0a, 0xa2, 0x1e, 0x1f, 0x75, 0x71, 0x7c, 0x05, + 0xd3, 0x37, 0xe7, 0x67, 0x57, 0x69, 0x71, 0x7b, 0x9d, 0x05, 0xb5, 0xed, 0xda, 0xce, 0xac, 0x19, + 0x08, 0x18, 0x40, 0x3d, 0x4f, 0xef, 0x2e, 0x3a, 0xe9, 0x69, 0x30, 0x56, 0x79, 0xfd, 0x23, 0x7e, + 0x85, 0x89, 0xe2, 0x2e, 0xcf, 0x82, 0xf1, 0xed, 0xda, 0xce, 0xfc, 0xe7, 0x0f, 0xcd, 0xfe, 0x7d, + 0xcd, 0xa7, 0xef, 0x6a, 0xba, 0xbb, 0x3c, 0x33, 0x55, 0x59, 0xe3, 0xd7, 0x24, 0x4c, 0x94, 0x47, + 0x9c, 0x81, 0x7a, 0xa2, 0xbe, 0x29, 0xfd, 0x5d, 0xb1, 0xff, 0x90, 0xc1, 0xac, 0x38, 0xe0, 0xce, + 0x47, 0x64, 0x2d, 0xdf, 0x27, 0x56, 0x43, 0x84, 0x79, 0xa1, 0x95, 0xe3, 0xc2, 0xf9, 0x24, 0x0e, + 0xb9, 0x23, 0x36, 0x86, 0x1b, 0xb0, 0x1a, 0x51, 0xb4, 0x4b, 0xc6, 0x1e, 0xc8, 0xb8, 0x27, 0xdf, + 0x97, 0x8c, 0xe3, 0x32, 0x2c, 0xc4, 0x5c, 0x1a, 0x2f, 0x95, 0x75, 0xbc, 0xd5, 0xe2, 0x4e, 0x6a, + 0xc5, 0x26, 0x4a, 0xd9, 0xb6, 0x95, 0x78, 0x2c, 0xff, 0x8f, 0x6f, 0x60, 0xcb, 0xd0, 0x51, 0x42, + 0xd6, 0x79, 0x1e, 0x86, 0x86, 0xac, 0xf5, 0x7b, 0xda, 0x78, 0x67, 0xb8, 0xb2, 0x5c, 0x54, 0xd0, + 0x24, 0x7e, 0x84, 0x77, 0x5c, 0x08, 0x8a, 0x9d, 0x7f, 0x8e, 0xad, 0xe3, 0x27, 0x78, 0x1f, 0x92, + 0x68, 0x49, 0x45, 0xcf, 0xc2, 0x53, 0xb8, 0x02, 0x8b, 0x7d, 0xe8, 0xa1, 0x31, 0x8d, 0x4b, 0xc0, + 0x2c, 0xa9, 0xf0, 0x91, 0x0a, 0xb8, 0x05, 0xeb, 0x7f, 0xf7, 0x7e, 0x08, 0xcc, 0x94, 0xd1, 0x0c, + 0x0d, 0xe9, 0x7b, 0x01, 0xb2, 0xd9, 0xd1, 0x36, 0x17, 0x42, 0x27, 0xca, 0xb1, 0x39, 0x7c, 0x0d, + 0x1b, 0xc3, 0x76, 0x9c, 0xec, 0xb6, 0xa4, 0xf0, 0xe5, 0x5e, 0xd8, 0x3c, 0x6e, 0xc2, 0x5a, 0x7f, + 0x1f, 0x42, 0x87, 0xe4, 0x79, 0x78, 0x4c, 0xc6, 0x49, 0x4b, 0x11, 0x29, 0xc7, 0x5e, 0x60, 0x03, + 0x36, 0xe3, 0xc4, 0x1e, 0x78, 0xa5, 0x9d, 0xdc, 0x93, 0xa2, 0xdb, 0xc2, 0xd0, 0xbe, 0xb4, 0xce, + 0x74, 0x23, 0x67, 0x65, 0x42, 0xff, 0x66, 0xbc, 0x21, 0x1b, 0x6b, 0x65, 0x89, 0x2d, 0xe0, 0x3a, + 0xac, 0x0c, 0xc3, 0x47, 0x09, 0x99, 0x36, 0x43, 0x7c, 0x0b, 0xdb, 0x4f, 0x98, 0x83, 0x16, 0x8b, + 0xe5, 0xd4, 0xa3, 0xee, 0xab, 0xf2, 0x63, 0x4b, 0xe5, 0x48, 0xa3, 0xec, 0x5e, 0xf9, 0x72, 0xf9, + 0x09, 0x52, 0xa4, 0x0f, 0xa5, 0x37, 0xd4, 0xcb, 0xf9, 0x25, 0xae, 0xc2, 0xf2, 0xbe, 0xd1, 0x49, + 0x5c, 0xc5, 0xe2, 0xa5, 0x3a, 0x96, 0xae, 0x3b, 0xdd, 0x0a, 0x2e, 0xc0, 0x5c, 0x57, 0x0c, 0x49, + 0x39, 0xe9, 0xda, 0x2c, 0x28, 0x69, 0xa1, 0xa3, 0x28, 0x51, 0xd2, 0xb5, 0x7d, 0x48, 0x56, 0x18, + 0x19, 0x57, 0xf4, 0x2a, 0x06, 0xb0, 0x34, 0xb0, 0x1e, 0xf4, 0x59, 0x2b, 0xdf, 0x7a, 0xe0, 0xdc, + 0x6f, 0x5b, 0xfb, 0x43, 0x2d, 0x15, 0x5b, 0x3f, 0x99, 0xac, 0x7e, 0xb8, 0x2f, 0x7f, 0x02, 0x00, + 0x00, 0xff, 0xff, 0xf0, 0x9e, 0xc6, 0x34, 0x0d, 0x04, 0x00, 0x00, } diff --git a/protocol/protobuf/application_metadata_message.proto b/protocol/protobuf/application_metadata_message.proto index d37caf40b..70a2f6d86 100644 --- a/protocol/protobuf/application_metadata_message.proto +++ b/protocol/protobuf/application_metadata_message.proto @@ -39,5 +39,6 @@ message ApplicationMetadataMessage { CHAT_IDENTITY = 24; COMMUNITY_DESCRIPTION = 25; COMMUNITY_INVITATION = 26; + COMMUNITY_REQUEST_TO_JOIN = 27; } } diff --git a/protocol/protobuf/communities.pb.go b/protocol/protobuf/communities.pb.go index f5ae00d09..db1c7d95c 100644 --- a/protocol/protobuf/communities.pb.go +++ b/protocol/protobuf/communities.pb.go @@ -20,6 +20,34 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package +type CommunityMember_Roles int32 + +const ( + CommunityMember_UNKNOWN_ROLE CommunityMember_Roles = 0 + CommunityMember_ROLE_ALL CommunityMember_Roles = 1 + CommunityMember_ROLE_MANAGE_USERS CommunityMember_Roles = 2 +) + +var CommunityMember_Roles_name = map[int32]string{ + 0: "UNKNOWN_ROLE", + 1: "ROLE_ALL", + 2: "ROLE_MANAGE_USERS", +} + +var CommunityMember_Roles_value = map[string]int32{ + "UNKNOWN_ROLE": 0, + "ROLE_ALL": 1, + "ROLE_MANAGE_USERS": 2, +} + +func (x CommunityMember_Roles) String() string { + return proto.EnumName(CommunityMember_Roles_name, int32(x)) +} + +func (CommunityMember_Roles) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_f937943d74c1cd8b, []int{1, 0} +} + type CommunityPermissions_Access int32 const ( @@ -115,9 +143,10 @@ func (m *Grant) GetClock() uint64 { } type CommunityMember struct { - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + Roles []CommunityMember_Roles `protobuf:"varint,1,rep,packed,name=roles,proto3,enum=protobuf.CommunityMember_Roles" json:"roles,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *CommunityMember) Reset() { *m = CommunityMember{} } @@ -145,6 +174,13 @@ func (m *CommunityMember) XXX_DiscardUnknown() { var xxx_messageInfo_CommunityMember proto.InternalMessageInfo +func (m *CommunityMember) GetRoles() []CommunityMember_Roles { + if m != nil { + return m.Roles + } + return nil +} + type CommunityPermissions struct { EnsOnly bool `protobuf:"varint,1,opt,name=ens_only,json=ensOnly,proto3" json:"ens_only,omitempty"` // https://gitlab.matrix.org/matrix-org/olm/blob/master/docs/megolm.md is a candidate for the algorithm to be used in case we want to have private communityal chats, lighter than pairwise encryption using the DR, less secure, but more efficient for large number of participants @@ -390,110 +426,126 @@ func (m *CommunityInvitation) GetPublicKey() []byte { return nil } -type CommunityRequestJoin struct { - EnsName string `protobuf:"bytes,1,opt,name=ens_name,json=ensName,proto3" json:"ens_name,omitempty"` - ChatId string `protobuf:"bytes,2,opt,name=chat_id,json=chatId,proto3" json:"chat_id,omitempty"` - CommunityId []byte `protobuf:"bytes,3,opt,name=community_id,json=communityId,proto3" json:"community_id,omitempty"` +type CommunityRequestToJoin struct { + Clock uint64 `protobuf:"varint,1,opt,name=clock,proto3" json:"clock,omitempty"` + EnsName string `protobuf:"bytes,2,opt,name=ens_name,json=ensName,proto3" json:"ens_name,omitempty"` + ChatId string `protobuf:"bytes,3,opt,name=chat_id,json=chatId,proto3" json:"chat_id,omitempty"` + CommunityId []byte `protobuf:"bytes,4,opt,name=community_id,json=communityId,proto3" json:"community_id,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } -func (m *CommunityRequestJoin) Reset() { *m = CommunityRequestJoin{} } -func (m *CommunityRequestJoin) String() string { return proto.CompactTextString(m) } -func (*CommunityRequestJoin) ProtoMessage() {} -func (*CommunityRequestJoin) Descriptor() ([]byte, []int) { +func (m *CommunityRequestToJoin) Reset() { *m = CommunityRequestToJoin{} } +func (m *CommunityRequestToJoin) String() string { return proto.CompactTextString(m) } +func (*CommunityRequestToJoin) ProtoMessage() {} +func (*CommunityRequestToJoin) Descriptor() ([]byte, []int) { return fileDescriptor_f937943d74c1cd8b, []int{6} } -func (m *CommunityRequestJoin) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_CommunityRequestJoin.Unmarshal(m, b) +func (m *CommunityRequestToJoin) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CommunityRequestToJoin.Unmarshal(m, b) } -func (m *CommunityRequestJoin) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_CommunityRequestJoin.Marshal(b, m, deterministic) +func (m *CommunityRequestToJoin) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CommunityRequestToJoin.Marshal(b, m, deterministic) } -func (m *CommunityRequestJoin) XXX_Merge(src proto.Message) { - xxx_messageInfo_CommunityRequestJoin.Merge(m, src) +func (m *CommunityRequestToJoin) XXX_Merge(src proto.Message) { + xxx_messageInfo_CommunityRequestToJoin.Merge(m, src) } -func (m *CommunityRequestJoin) XXX_Size() int { - return xxx_messageInfo_CommunityRequestJoin.Size(m) +func (m *CommunityRequestToJoin) XXX_Size() int { + return xxx_messageInfo_CommunityRequestToJoin.Size(m) } -func (m *CommunityRequestJoin) XXX_DiscardUnknown() { - xxx_messageInfo_CommunityRequestJoin.DiscardUnknown(m) +func (m *CommunityRequestToJoin) XXX_DiscardUnknown() { + xxx_messageInfo_CommunityRequestToJoin.DiscardUnknown(m) } -var xxx_messageInfo_CommunityRequestJoin proto.InternalMessageInfo +var xxx_messageInfo_CommunityRequestToJoin proto.InternalMessageInfo -func (m *CommunityRequestJoin) GetEnsName() string { +func (m *CommunityRequestToJoin) GetClock() uint64 { + if m != nil { + return m.Clock + } + return 0 +} + +func (m *CommunityRequestToJoin) GetEnsName() string { if m != nil { return m.EnsName } return "" } -func (m *CommunityRequestJoin) GetChatId() string { +func (m *CommunityRequestToJoin) GetChatId() string { if m != nil { return m.ChatId } return "" } -func (m *CommunityRequestJoin) GetCommunityId() []byte { +func (m *CommunityRequestToJoin) GetCommunityId() []byte { if m != nil { return m.CommunityId } return nil } -type CommunityRequestJoinResponse struct { - Community *CommunityDescription `protobuf:"bytes,1,opt,name=community,proto3" json:"community,omitempty"` - Accepted bool `protobuf:"varint,2,opt,name=accepted,proto3" json:"accepted,omitempty"` - Grant []byte `protobuf:"bytes,3,opt,name=grant,proto3" json:"grant,omitempty"` +type CommunityRequestToJoinResponse struct { + Clock uint64 `protobuf:"varint,1,opt,name=clock,proto3" json:"clock,omitempty"` + Community *CommunityDescription `protobuf:"bytes,2,opt,name=community,proto3" json:"community,omitempty"` + Accepted bool `protobuf:"varint,3,opt,name=accepted,proto3" json:"accepted,omitempty"` + Grant []byte `protobuf:"bytes,4,opt,name=grant,proto3" json:"grant,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } -func (m *CommunityRequestJoinResponse) Reset() { *m = CommunityRequestJoinResponse{} } -func (m *CommunityRequestJoinResponse) String() string { return proto.CompactTextString(m) } -func (*CommunityRequestJoinResponse) ProtoMessage() {} -func (*CommunityRequestJoinResponse) Descriptor() ([]byte, []int) { +func (m *CommunityRequestToJoinResponse) Reset() { *m = CommunityRequestToJoinResponse{} } +func (m *CommunityRequestToJoinResponse) String() string { return proto.CompactTextString(m) } +func (*CommunityRequestToJoinResponse) ProtoMessage() {} +func (*CommunityRequestToJoinResponse) Descriptor() ([]byte, []int) { return fileDescriptor_f937943d74c1cd8b, []int{7} } -func (m *CommunityRequestJoinResponse) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_CommunityRequestJoinResponse.Unmarshal(m, b) +func (m *CommunityRequestToJoinResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CommunityRequestToJoinResponse.Unmarshal(m, b) } -func (m *CommunityRequestJoinResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_CommunityRequestJoinResponse.Marshal(b, m, deterministic) +func (m *CommunityRequestToJoinResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CommunityRequestToJoinResponse.Marshal(b, m, deterministic) } -func (m *CommunityRequestJoinResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_CommunityRequestJoinResponse.Merge(m, src) +func (m *CommunityRequestToJoinResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_CommunityRequestToJoinResponse.Merge(m, src) } -func (m *CommunityRequestJoinResponse) XXX_Size() int { - return xxx_messageInfo_CommunityRequestJoinResponse.Size(m) +func (m *CommunityRequestToJoinResponse) XXX_Size() int { + return xxx_messageInfo_CommunityRequestToJoinResponse.Size(m) } -func (m *CommunityRequestJoinResponse) XXX_DiscardUnknown() { - xxx_messageInfo_CommunityRequestJoinResponse.DiscardUnknown(m) +func (m *CommunityRequestToJoinResponse) XXX_DiscardUnknown() { + xxx_messageInfo_CommunityRequestToJoinResponse.DiscardUnknown(m) } -var xxx_messageInfo_CommunityRequestJoinResponse proto.InternalMessageInfo +var xxx_messageInfo_CommunityRequestToJoinResponse proto.InternalMessageInfo -func (m *CommunityRequestJoinResponse) GetCommunity() *CommunityDescription { +func (m *CommunityRequestToJoinResponse) GetClock() uint64 { + if m != nil { + return m.Clock + } + return 0 +} + +func (m *CommunityRequestToJoinResponse) GetCommunity() *CommunityDescription { if m != nil { return m.Community } return nil } -func (m *CommunityRequestJoinResponse) GetAccepted() bool { +func (m *CommunityRequestToJoinResponse) GetAccepted() bool { if m != nil { return m.Accepted } return false } -func (m *CommunityRequestJoinResponse) GetGrant() []byte { +func (m *CommunityRequestToJoinResponse) GetGrant() []byte { if m != nil { return m.Grant } @@ -501,6 +553,7 @@ func (m *CommunityRequestJoinResponse) GetGrant() []byte { } func init() { + proto.RegisterEnum("protobuf.CommunityMember_Roles", CommunityMember_Roles_name, CommunityMember_Roles_value) proto.RegisterEnum("protobuf.CommunityPermissions_Access", CommunityPermissions_Access_name, CommunityPermissions_Access_value) proto.RegisterType((*Grant)(nil), "protobuf.Grant") proto.RegisterType((*CommunityMember)(nil), "protobuf.CommunityMember") @@ -511,8 +564,8 @@ func init() { proto.RegisterType((*CommunityChat)(nil), "protobuf.CommunityChat") proto.RegisterMapType((map[string]*CommunityMember)(nil), "protobuf.CommunityChat.MembersEntry") proto.RegisterType((*CommunityInvitation)(nil), "protobuf.CommunityInvitation") - proto.RegisterType((*CommunityRequestJoin)(nil), "protobuf.CommunityRequestJoin") - proto.RegisterType((*CommunityRequestJoinResponse)(nil), "protobuf.CommunityRequestJoinResponse") + proto.RegisterType((*CommunityRequestToJoin)(nil), "protobuf.CommunityRequestToJoin") + proto.RegisterType((*CommunityRequestToJoinResponse)(nil), "protobuf.CommunityRequestToJoinResponse") } func init() { @@ -520,47 +573,51 @@ func init() { } var fileDescriptor_f937943d74c1cd8b = []byte{ - // 662 bytes of a gzipped FileDescriptorProto + // 734 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x54, 0xdd, 0x6e, 0xd3, 0x4a, - 0x10, 0x3e, 0xb6, 0x9b, 0xc4, 0x99, 0xf4, 0x27, 0xdd, 0xf6, 0x9c, 0xba, 0x39, 0x80, 0x82, 0x05, - 0x52, 0x10, 0x22, 0x48, 0xe9, 0x0d, 0x42, 0xfc, 0x95, 0x62, 0x81, 0x29, 0x75, 0xda, 0x4d, 0x0a, - 0xe2, 0xca, 0x72, 0xec, 0x05, 0xac, 0xc6, 0x6b, 0xe3, 0x75, 0x22, 0xe5, 0x25, 0xb8, 0xe6, 0x82, - 0xc7, 0xe2, 0x01, 0x78, 0x14, 0xe4, 0x5d, 0x3b, 0x76, 0xc1, 0x85, 0x4a, 0x88, 0xab, 0x64, 0x77, - 0xf6, 0xfb, 0x66, 0xe6, 0x9b, 0xf1, 0x07, 0x9b, 0x6e, 0x18, 0x04, 0x33, 0xea, 0x27, 0x3e, 0x61, - 0xfd, 0x28, 0x0e, 0x93, 0x10, 0xa9, 0xfc, 0x67, 0x32, 0x7b, 0xd7, 0xd9, 0x72, 0x3f, 0x38, 0x89, - 0xed, 0x7b, 0x84, 0x26, 0x7e, 0xb2, 0x10, 0x61, 0x7d, 0x0e, 0xb5, 0xe7, 0xb1, 0x43, 0x13, 0x74, - 0x1d, 0x56, 0x73, 0xf0, 0xc2, 0xf6, 0x3d, 0x4d, 0xea, 0x4a, 0xbd, 0x55, 0xdc, 0x5a, 0xde, 0x99, - 0x1e, 0xfa, 0x1f, 0x9a, 0x01, 0x09, 0x26, 0x24, 0x4e, 0xe3, 0x32, 0x8f, 0xab, 0xe2, 0xc2, 0xf4, - 0xd0, 0x0e, 0x34, 0x32, 0x7e, 0x4d, 0xe9, 0x4a, 0xbd, 0x26, 0xae, 0xa7, 0x47, 0xd3, 0x43, 0xdb, - 0x50, 0x73, 0xa7, 0xa1, 0x7b, 0xa6, 0xad, 0x74, 0xa5, 0xde, 0x0a, 0x16, 0x07, 0x7d, 0x13, 0x36, - 0x0e, 0x72, 0xea, 0x23, 0xce, 0xa1, 0x7f, 0x93, 0x60, 0x7b, 0x79, 0x77, 0x4c, 0xe2, 0xc0, 0x67, - 0xcc, 0x0f, 0x29, 0x43, 0xbb, 0xa0, 0x12, 0xca, 0xec, 0x90, 0x4e, 0x17, 0xbc, 0x2c, 0x15, 0x37, - 0x08, 0x65, 0x43, 0x3a, 0x5d, 0x20, 0x0d, 0x1a, 0x51, 0xec, 0xcf, 0x9d, 0x84, 0xf0, 0x82, 0x54, - 0x9c, 0x1f, 0xd1, 0x43, 0xa8, 0x3b, 0xae, 0x4b, 0x18, 0xe3, 0xe5, 0xac, 0x0f, 0x6e, 0xf6, 0x73, - 0x21, 0xfa, 0x55, 0x49, 0xfa, 0xfb, 0xfc, 0x31, 0xce, 0x40, 0xfa, 0x18, 0xea, 0xe2, 0x06, 0x21, - 0x58, 0x3f, 0xb5, 0x0e, 0xad, 0xe1, 0x1b, 0xcb, 0xde, 0x3f, 0x38, 0x30, 0x46, 0xa3, 0xf6, 0x3f, - 0x68, 0x13, 0xd6, 0xac, 0xa1, 0x7d, 0x64, 0x1c, 0x3d, 0x35, 0xf0, 0xe8, 0x85, 0x79, 0xdc, 0x96, - 0xd0, 0x16, 0x6c, 0x98, 0xd6, 0x6b, 0x73, 0xbc, 0x3f, 0x36, 0x87, 0x96, 0x3d, 0xb4, 0x5e, 0xbd, - 0x6d, 0xcb, 0x68, 0x1d, 0x60, 0x68, 0xd9, 0xd8, 0x38, 0x39, 0x35, 0x46, 0xe3, 0xb6, 0xa2, 0x7f, - 0x55, 0x4a, 0x2d, 0x3e, 0x23, 0xcc, 0x8d, 0xfd, 0x28, 0xf1, 0x43, 0x5a, 0x88, 0x24, 0x95, 0x44, - 0x42, 0x06, 0x34, 0x84, 0xbe, 0x4c, 0x93, 0xbb, 0x4a, 0xaf, 0x35, 0xb8, 0x5d, 0xd1, 0x44, 0x89, - 0xa6, 0x2f, 0x94, 0x64, 0x06, 0x4d, 0xe2, 0x05, 0xce, 0xb1, 0xe8, 0x09, 0xb4, 0xa2, 0xa2, 0x53, - 0xae, 0x47, 0x6b, 0x70, 0xed, 0xd7, 0x7a, 0xe0, 0x32, 0x04, 0x0d, 0x40, 0xcd, 0xf7, 0x46, 0xab, - 0x71, 0xf8, 0x7f, 0x25, 0x38, 0x9f, 0xb3, 0x88, 0xe2, 0xe5, 0x3b, 0xf4, 0x18, 0x6a, 0xe9, 0x06, - 0x30, 0xad, 0xce, 0x4b, 0xbf, 0xf5, 0x9b, 0xd2, 0x53, 0x96, 0xac, 0x70, 0x81, 0xeb, 0x9c, 0xc2, - 0x6a, 0xb9, 0x1f, 0xd4, 0x06, 0xe5, 0x8c, 0x88, 0x0d, 0x68, 0xe2, 0xf4, 0x2f, 0xba, 0x0b, 0xb5, - 0xb9, 0x33, 0x9d, 0x89, 0xd9, 0xb7, 0x06, 0xbb, 0x15, 0x29, 0x04, 0x03, 0x16, 0xef, 0xee, 0xcb, - 0xf7, 0xa4, 0xce, 0x09, 0x40, 0x91, 0xab, 0x82, 0xf4, 0xce, 0x79, 0xd2, 0x9d, 0x0a, 0xd2, 0x14, - 0x5f, 0xa2, 0xd4, 0xbf, 0xc8, 0xb0, 0x76, 0x2e, 0x88, 0x1e, 0x15, 0x93, 0x93, 0x78, 0xfb, 0x37, - 0x2e, 0xa0, 0xb9, 0xdc, 0xc8, 0xe4, 0x3f, 0x1b, 0x99, 0x72, 0xb9, 0x91, 0xfd, 0x25, 0xc5, 0xf5, - 0xcf, 0x12, 0x6c, 0x2d, 0xc3, 0x26, 0x9d, 0xfb, 0x89, 0xc3, 0x97, 0x7e, 0x0f, 0xfe, 0x2d, 0x2c, - 0xc7, 0x2b, 0x76, 0x21, 0xf3, 0x9e, 0x6d, 0xf7, 0x82, 0x2f, 0xe5, 0x7d, 0x6a, 0x58, 0x99, 0x01, - 0x89, 0xc3, 0xc5, 0xee, 0x73, 0x15, 0x20, 0x9a, 0x4d, 0xa6, 0xbe, 0x6b, 0xa7, 0x9d, 0xac, 0x70, - 0x4c, 0x53, 0xdc, 0x1c, 0x92, 0x85, 0x1e, 0x94, 0xbe, 0x47, 0x4c, 0x3e, 0xce, 0x08, 0x4b, 0x5e, - 0x86, 0x3e, 0xcd, 0x2d, 0x87, 0x3a, 0x01, 0xc9, 0xda, 0x4f, 0x2d, 0xc7, 0x72, 0x02, 0x52, 0x4e, - 0x25, 0x9f, 0x4b, 0xf5, 0xa3, 0x83, 0x2a, 0x3f, 0x39, 0xa8, 0xfe, 0x49, 0x82, 0x2b, 0x55, 0xf9, - 0x30, 0x61, 0x51, 0x48, 0x19, 0x41, 0x0f, 0xa0, 0xb9, 0x7c, 0xcf, 0x13, 0x57, 0x4f, 0xbd, 0x24, - 0x08, 0x2e, 0x00, 0xa8, 0x03, 0x6a, 0x6a, 0x5f, 0x51, 0x42, 0xbc, 0xcc, 0x0e, 0x97, 0xe7, 0x42, - 0x37, 0xa5, 0xa4, 0xdb, 0xa4, 0xce, 0xb9, 0xf7, 0xbe, 0x07, 0x00, 0x00, 0xff, 0xff, 0x84, 0x86, - 0xd5, 0x32, 0x39, 0x06, 0x00, 0x00, + 0x10, 0xee, 0xe6, 0xd7, 0x99, 0xa4, 0xa9, 0xbb, 0xfd, 0x73, 0x73, 0x74, 0x7a, 0x72, 0x2c, 0x90, + 0x82, 0x10, 0x41, 0x4a, 0x85, 0x84, 0x10, 0x14, 0x42, 0xb1, 0x8a, 0x69, 0xe2, 0xb4, 0x9b, 0x04, + 0xc4, 0x95, 0xe5, 0x38, 0x0b, 0x58, 0x4d, 0xec, 0xe0, 0x75, 0x22, 0xe5, 0x01, 0x90, 0x78, 0x04, + 0x2e, 0xb8, 0xe6, 0x89, 0x78, 0x00, 0x1e, 0x05, 0x79, 0x37, 0x89, 0xdd, 0x92, 0x94, 0x4a, 0x88, + 0x2b, 0x7b, 0x76, 0x66, 0xbe, 0x9d, 0xf9, 0x66, 0xf6, 0x83, 0x4d, 0xdb, 0x1b, 0x0e, 0xc7, 0xae, + 0x13, 0x38, 0x94, 0x55, 0x47, 0xbe, 0x17, 0x78, 0x58, 0xe2, 0x9f, 0xde, 0xf8, 0x5d, 0x69, 0xcb, + 0xfe, 0x60, 0x05, 0xa6, 0xd3, 0xa7, 0x6e, 0xe0, 0x04, 0x53, 0xe1, 0x56, 0x27, 0x90, 0x3e, 0xf1, + 0x2d, 0x37, 0xc0, 0xff, 0x43, 0x61, 0x9e, 0x3c, 0x35, 0x9d, 0xbe, 0x82, 0xca, 0xa8, 0x52, 0x20, + 0xf9, 0xc5, 0x99, 0xde, 0xc7, 0xff, 0x40, 0x6e, 0x48, 0x87, 0x3d, 0xea, 0x87, 0xfe, 0x04, 0xf7, + 0x4b, 0xe2, 0x40, 0xef, 0xe3, 0x3d, 0xc8, 0xce, 0xf0, 0x95, 0x64, 0x19, 0x55, 0x72, 0x24, 0x13, + 0x9a, 0x7a, 0x1f, 0x6f, 0x43, 0xda, 0x1e, 0x78, 0xf6, 0x85, 0x92, 0x2a, 0xa3, 0x4a, 0x8a, 0x08, + 0x43, 0xfd, 0x8c, 0x60, 0xe3, 0x78, 0x8e, 0xdd, 0xe4, 0x20, 0xf8, 0x01, 0xa4, 0x7d, 0x6f, 0x40, + 0x99, 0x82, 0xca, 0xc9, 0x4a, 0xb1, 0xf6, 0x5f, 0x75, 0x5e, 0x7a, 0xf5, 0x4a, 0x64, 0x95, 0x84, + 0x61, 0x44, 0x44, 0xab, 0x47, 0x90, 0xe6, 0x36, 0x96, 0xa1, 0xd0, 0x35, 0x4e, 0x8d, 0xd6, 0x1b, + 0xc3, 0x24, 0xad, 0x86, 0x26, 0xaf, 0xe1, 0x02, 0x48, 0xe1, 0x9f, 0x59, 0x6f, 0x34, 0x64, 0x84, + 0x77, 0x60, 0x93, 0x5b, 0xcd, 0xba, 0x51, 0x3f, 0xd1, 0xcc, 0x6e, 0x5b, 0x23, 0x6d, 0x39, 0xa1, + 0xfe, 0x40, 0xb0, 0xbd, 0xb8, 0xe0, 0x8c, 0xfa, 0x43, 0x87, 0x31, 0xc7, 0x73, 0x19, 0xde, 0x07, + 0x89, 0xba, 0xcc, 0xf4, 0xdc, 0xc1, 0x94, 0xd3, 0x21, 0x91, 0x2c, 0x75, 0x59, 0xcb, 0x1d, 0x4c, + 0xb1, 0x02, 0xd9, 0x91, 0xef, 0x4c, 0xac, 0x80, 0x72, 0x22, 0x24, 0x32, 0x37, 0xf1, 0x13, 0xc8, + 0x58, 0xb6, 0x4d, 0x19, 0xe3, 0x34, 0x14, 0x6b, 0xb7, 0x97, 0x74, 0x11, 0xbb, 0xa4, 0x5a, 0xe7, + 0xc1, 0x64, 0x96, 0xa4, 0x76, 0x20, 0x23, 0x4e, 0x30, 0x86, 0xe2, 0xbc, 0x9b, 0xfa, 0xf1, 0xb1, + 0xd6, 0x6e, 0xcb, 0x6b, 0x78, 0x13, 0xd6, 0x8d, 0x96, 0xd9, 0xd4, 0x9a, 0xcf, 0x35, 0xd2, 0x7e, + 0xa9, 0x9f, 0xc9, 0x08, 0x6f, 0xc1, 0x86, 0x6e, 0xbc, 0xd6, 0x3b, 0xf5, 0x8e, 0xde, 0x32, 0xcc, + 0x96, 0xd1, 0x78, 0x2b, 0x27, 0x70, 0x11, 0xa0, 0x65, 0x98, 0x44, 0x3b, 0xef, 0x6a, 0xed, 0x8e, + 0x9c, 0x54, 0xbf, 0x27, 0x63, 0x2d, 0xbe, 0xa0, 0xcc, 0xf6, 0x9d, 0x51, 0xe0, 0x78, 0x6e, 0x34, + 0x1c, 0x14, 0x1b, 0x0e, 0xd6, 0x20, 0x2b, 0xe6, 0xca, 0x94, 0x44, 0x39, 0x59, 0xc9, 0xd7, 0xee, + 0x2e, 0x69, 0x22, 0x06, 0x53, 0x15, 0x63, 0x61, 0x9a, 0x1b, 0xf8, 0x53, 0x32, 0xcf, 0xc5, 0xcf, + 0x20, 0x3f, 0x8a, 0x3a, 0xe5, 0x7c, 0xe4, 0x6b, 0x07, 0xd7, 0xf3, 0x41, 0xe2, 0x29, 0xb8, 0x06, + 0xd2, 0x7c, 0x5f, 0x95, 0x34, 0x4f, 0xdf, 0x8d, 0xa5, 0xf3, 0xfd, 0x12, 0x5e, 0xb2, 0x88, 0xc3, + 0x4f, 0x21, 0x1d, 0x6e, 0x1e, 0x53, 0x32, 0xbc, 0xf4, 0x3b, 0xbf, 0x29, 0x3d, 0x44, 0x99, 0x15, + 0x2e, 0xf2, 0x4a, 0x5d, 0x28, 0xc4, 0xfb, 0xc1, 0x32, 0x24, 0x2f, 0xa8, 0xd8, 0x80, 0x1c, 0x09, + 0x7f, 0xf1, 0x7d, 0x48, 0x4f, 0xac, 0xc1, 0x58, 0xcc, 0x3e, 0x5f, 0xdb, 0x5f, 0xb9, 0xa8, 0x44, + 0xc4, 0x3d, 0x4a, 0x3c, 0x44, 0xa5, 0x73, 0x80, 0xe8, 0xae, 0x25, 0xa0, 0xf7, 0x2e, 0x83, 0xee, + 0x2d, 0x01, 0x0d, 0xf3, 0x63, 0x90, 0xea, 0xd7, 0x04, 0xac, 0x5f, 0x72, 0xe2, 0xa3, 0x68, 0x72, + 0x88, 0xb7, 0x7f, 0x6b, 0x05, 0xcc, 0xcd, 0x46, 0x96, 0xf8, 0xb3, 0x91, 0x25, 0x6f, 0x36, 0xb2, + 0xbf, 0xc4, 0xb8, 0xfa, 0x05, 0xc1, 0xd6, 0xc2, 0xad, 0xbb, 0x13, 0x27, 0xb0, 0xf8, 0xd2, 0x1f, + 0xc2, 0x4e, 0x24, 0x75, 0xfd, 0x68, 0x17, 0x66, 0x9a, 0xb7, 0x6d, 0xaf, 0x78, 0x29, 0xef, 0x43, + 0xa1, 0x9c, 0x09, 0x9f, 0x30, 0x56, 0xab, 0xde, 0xbf, 0x00, 0xa3, 0x71, 0x6f, 0xe0, 0xd8, 0x66, + 0xd8, 0x49, 0x8a, 0xe7, 0xe4, 0xc4, 0xc9, 0x29, 0x9d, 0xaa, 0x9f, 0x10, 0xec, 0x2e, 0x4a, 0x23, + 0xf4, 0xe3, 0x98, 0xb2, 0xa0, 0xe3, 0xbd, 0xf2, 0x9c, 0x55, 0x4f, 0x72, 0xa6, 0x45, 0xae, 0x35, + 0x14, 0x1c, 0xe4, 0xb8, 0x16, 0x19, 0xd6, 0x90, 0xae, 0xae, 0xe1, 0xaa, 0xa4, 0xa7, 0x7e, 0x91, + 0x74, 0xf5, 0x1b, 0x82, 0x83, 0xe5, 0x75, 0x10, 0xca, 0x46, 0x9e, 0xcb, 0xe8, 0x8a, 0x7a, 0x1e, + 0x43, 0x6e, 0x81, 0x73, 0xcd, 0x9a, 0xc4, 0x18, 0x24, 0x51, 0x02, 0x2e, 0x81, 0x14, 0xea, 0xdd, + 0x28, 0xa0, 0xa2, 0x66, 0x89, 0x2c, 0xec, 0x88, 0xe8, 0x54, 0x8c, 0xe8, 0x5e, 0x86, 0x63, 0x1f, + 0xfe, 0x0c, 0x00, 0x00, 0xff, 0xff, 0x1a, 0x7c, 0x38, 0x45, 0xe2, 0x06, 0x00, 0x00, } diff --git a/protocol/protobuf/communities.proto b/protocol/protobuf/communities.proto index 521bf14ee..992fb1c05 100644 --- a/protocol/protobuf/communities.proto +++ b/protocol/protobuf/communities.proto @@ -12,6 +12,12 @@ message Grant { } message CommunityMember { + enum Roles { + UNKNOWN_ROLE = 0; + ROLE_ALL = 1; + ROLE_MANAGE_USERS = 2; + } + repeated Roles roles = 1; } message CommunityPermissions { @@ -49,14 +55,16 @@ message CommunityInvitation { bytes public_key = 4; } -message CommunityRequestJoin { - string ens_name = 1; - string chat_id = 2; - bytes community_id = 3; +message CommunityRequestToJoin { + uint64 clock = 1; + string ens_name = 2; + string chat_id = 3; + bytes community_id = 4; } -message CommunityRequestJoinResponse { - CommunityDescription community = 1; - bool accepted = 2; - bytes grant = 3; +message CommunityRequestToJoinResponse { + uint64 clock = 1; + CommunityDescription community = 2; + bool accepted = 3; + bytes grant = 4; } diff --git a/protocol/push_notification_test.go b/protocol/push_notification_test.go index ab47dc5b4..dcec9158c 100644 --- a/protocol/push_notification_test.go +++ b/protocol/push_notification_test.go @@ -185,8 +185,8 @@ func (s *MessengerPushNotificationSuite) TestReceivePushNotification() { // Create one to one chat & send message pkString := hex.EncodeToString(crypto.FromECDSAPub(&s.m.identity.PublicKey)) chat := CreateOneToOneChat(pkString, &s.m.identity.PublicKey, alice.transport) - s.Require().NoError(alice.SaveChat(&chat)) - inputMessage := buildTestMessage(chat) + s.Require().NoError(alice.SaveChat(chat)) + inputMessage := buildTestMessage(*chat) response, err := alice.SendChatMessage(context.Background(), inputMessage) s.Require().NoError(err) messageIDString := response.Messages[0].ID @@ -344,8 +344,8 @@ func (s *MessengerPushNotificationSuite) TestReceivePushNotificationFromContactO // Create one to one chat & send message pkString := hex.EncodeToString(crypto.FromECDSAPub(&s.m.identity.PublicKey)) chat := CreateOneToOneChat(pkString, &s.m.identity.PublicKey, alice.transport) - s.Require().NoError(alice.SaveChat(&chat)) - inputMessage := buildTestMessage(chat) + s.Require().NoError(alice.SaveChat(chat)) + inputMessage := buildTestMessage(*chat) response, err := alice.SendChatMessage(context.Background(), inputMessage) s.Require().NoError(err) messageIDString := response.Messages[0].ID @@ -500,8 +500,8 @@ func (s *MessengerPushNotificationSuite) TestReceivePushNotificationRetries() { // Create one to one chat & send message pkString := hex.EncodeToString(crypto.FromECDSAPub(&s.m.identity.PublicKey)) chat := CreateOneToOneChat(pkString, &s.m.identity.PublicKey, alice.transport) - s.Require().NoError(alice.SaveChat(&chat)) - inputMessage := buildTestMessage(chat) + s.Require().NoError(alice.SaveChat(chat)) + inputMessage := buildTestMessage(*chat) _, err = alice.SendChatMessage(context.Background(), inputMessage) s.Require().NoError(err) @@ -571,7 +571,7 @@ func (s *MessengerPushNotificationSuite) TestReceivePushNotificationRetries() { s.Require().NotEqual(newBobServers[0].AccessToken, bobServers[0].AccessToken) // Send another message, here the token will not be valid - inputMessage = buildTestMessage(chat) + inputMessage = buildTestMessage(*chat) response, err := alice.SendChatMessage(context.Background(), inputMessage) s.Require().NoError(err) messageIDString := response.Messages[0].ID @@ -720,16 +720,16 @@ func (s *MessengerPushNotificationSuite) TestReceivePushNotificationMention() { // Create public chat and join for both alice and bob chat := CreatePublicChat("status", s.m.transport) - err = bob.SaveChat(&chat) + err = bob.SaveChat(chat) s.Require().NoError(err) - err = bob.Join(chat) + _, err = bob.Join(chat) s.Require().NoError(err) - err = alice.SaveChat(&chat) + err = alice.SaveChat(chat) s.Require().NoError(err) - err = alice.Join(chat) + _, err = alice.Join(chat) s.Require().NoError(err) // Register bob @@ -772,7 +772,7 @@ func (s *MessengerPushNotificationSuite) TestReceivePushNotificationMention() { bobServers, err := bob.GetPushNotificationsServers() s.Require().NoError(err) - inputMessage := buildTestMessage(chat) + inputMessage := buildTestMessage(*chat) // message contains a mention inputMessage.Text = "Hey @" + types.EncodeHex(crypto.FromECDSAPub(&bob.identity.PublicKey)) response, err := alice.SendChatMessage(context.Background(), inputMessage) diff --git a/protocol/pushnotificationserver/server.go b/protocol/pushnotificationserver/server.go index 0c126be7a..9f06d916f 100644 --- a/protocol/pushnotificationserver/server.go +++ b/protocol/pushnotificationserver/server.go @@ -460,7 +460,8 @@ func (s *Server) listenToPublicKeyQueryTopic(hashedPublicKey []byte) error { return nil } encodedPublicKey := hex.EncodeToString(hashedPublicKey) - return s.messageProcessor.JoinPublic(encodedPublicKey) + _, err := s.messageProcessor.JoinPublic(encodedPublicKey) + return err } // buildPushNotificationRegistrationResponse will check the registration is valid, save it, and listen to the topic for the queries diff --git a/protocol/requests/accept_request_to_join_community.go b/protocol/requests/accept_request_to_join_community.go new file mode 100644 index 000000000..03e09791a --- /dev/null +++ b/protocol/requests/accept_request_to_join_community.go @@ -0,0 +1,21 @@ +package requests + +import ( + "errors" + + "github.com/status-im/status-go/eth-node/types" +) + +var ErrAcceptRequestToJoinCommunityInvalidID = errors.New("accept-request-to-join-community: invalid id") + +type AcceptRequestToJoinCommunity struct { + ID types.HexBytes +} + +func (j *AcceptRequestToJoinCommunity) Validate() error { + if len(j.ID) == 0 { + return ErrAcceptRequestToJoinCommunityInvalidID + } + + return nil +} diff --git a/protocol/requests/create_community_request.go b/protocol/requests/create_community_request.go new file mode 100644 index 000000000..31f539379 --- /dev/null +++ b/protocol/requests/create_community_request.go @@ -0,0 +1,89 @@ +package requests + +import ( + "errors" + + "github.com/ethereum/go-ethereum/log" + userimages "github.com/status-im/status-go/images" + "github.com/status-im/status-go/protocol/images" + "github.com/status-im/status-go/protocol/protobuf" +) + +var ( + ErrCreateCommunityInvalidName = errors.New("create-community: invalid name") + ErrCreateCommunityInvalidColor = errors.New("create-community: invalid color") + ErrCreateCommunityInvalidDescription = errors.New("create-community: invalid description") + ErrCreateCommunityInvalidMembership = errors.New("create-community: invalid membership") +) + +type CreateCommunity struct { + Name string `json:"name"` + Description string `json:"description"` + Color string `json:"color"` + Membership protobuf.CommunityPermissions_Access `json:"membership"` + EnsOnly bool `json:"ensOnly"` + Image string `json:"image"` + ImageAx int `json:"imageAx"` + ImageAy int `json:"imageAy"` + ImageBx int `json:"imageBx"` + ImageBy int `json:"imageBy"` +} + +func adaptIdentityImageToProtobuf(img *userimages.IdentityImage) *protobuf.IdentityImage { + return &protobuf.IdentityImage{ + Payload: img.Payload, + SourceType: protobuf.IdentityImage_RAW_PAYLOAD, + ImageType: images.ImageType(img.Payload), + } +} + +func (c *CreateCommunity) Validate() error { + if c.Name == "" { + return ErrCreateCommunityInvalidName + } + + if c.Description == "" { + return ErrCreateCommunityInvalidDescription + } + + if c.Membership == protobuf.CommunityPermissions_UNKNOWN_ACCESS { + return ErrCreateCommunityInvalidMembership + } + + if c.Color == "" { + return ErrCreateCommunityInvalidColor + } + + return nil +} + +func (c *CreateCommunity) ToCommunityDescription() (*protobuf.CommunityDescription, error) { + ci := &protobuf.ChatIdentity{ + DisplayName: c.Name, + Color: c.Color, + Description: c.Description, + } + + if c.Image != "" { + log.Info("has-image", "image", c.Image) + ciis := make(map[string]*protobuf.IdentityImage) + imgs, err := userimages.GenerateIdentityImages(c.Image, c.ImageAx, c.ImageAy, c.ImageBx, c.ImageBy) + if err != nil { + return nil, err + } + for _, img := range imgs { + ciis[img.Name] = adaptIdentityImageToProtobuf(img) + } + ci.Images = ciis + log.Info("set images", "images", ci) + } + + description := &protobuf.CommunityDescription{ + Identity: ci, + Permissions: &protobuf.CommunityPermissions{ + Access: c.Membership, + EnsOnly: c.EnsOnly, + }, + } + return description, nil +} diff --git a/protocol/requests/create_one_to_one_chat.go b/protocol/requests/create_one_to_one_chat.go new file mode 100644 index 000000000..d05800880 --- /dev/null +++ b/protocol/requests/create_one_to_one_chat.go @@ -0,0 +1,21 @@ +package requests + +import ( + "errors" + + "github.com/status-im/status-go/eth-node/types" +) + +var ErrCreateOneToOneChatInvalidID = errors.New("create-one-to-one-chat: invalid id") + +type CreateOneToOneChat struct { + ID types.HexBytes +} + +func (j *CreateOneToOneChat) Validate() error { + if len(j.ID) == 0 { + return ErrCreateOneToOneChatInvalidID + } + + return nil +} diff --git a/protocol/requests/decline_request_to_join_community.go b/protocol/requests/decline_request_to_join_community.go new file mode 100644 index 000000000..fc22ac5b3 --- /dev/null +++ b/protocol/requests/decline_request_to_join_community.go @@ -0,0 +1,21 @@ +package requests + +import ( + "errors" + + "github.com/status-im/status-go/eth-node/types" +) + +var ErrDeclineRequestToJoinCommunityInvalidID = errors.New("accept-request-to-join-community: invalid id") + +type DeclineRequestToJoinCommunity struct { + ID types.HexBytes +} + +func (j *DeclineRequestToJoinCommunity) Validate() error { + if len(j.ID) == 0 { + return ErrDeclineRequestToJoinCommunityInvalidID + } + + return nil +} diff --git a/protocol/requests/invite_users_to_community.go b/protocol/requests/invite_users_to_community.go new file mode 100644 index 000000000..a1e84a9ce --- /dev/null +++ b/protocol/requests/invite_users_to_community.go @@ -0,0 +1,27 @@ +package requests + +import ( + "errors" + + "github.com/status-im/status-go/eth-node/types" +) + +var ErrInviteUsersToCommunityInvalidID = errors.New("invite-users-to-community: invalid id") +var ErrInviteUsersToCommunityEmptyUsers = errors.New("invite-users-to-community: empty users") + +type InviteUsersToCommunity struct { + CommunityID types.HexBytes + Users []types.HexBytes +} + +func (j *InviteUsersToCommunity) Validate() error { + if len(j.CommunityID) == 0 { + return ErrInviteUsersToCommunityInvalidID + } + + if len(j.Users) == 0 { + return ErrInviteUsersToCommunityEmptyUsers + } + + return nil +} diff --git a/protocol/requests/request_to_join_community.go b/protocol/requests/request_to_join_community.go new file mode 100644 index 000000000..a168c1975 --- /dev/null +++ b/protocol/requests/request_to_join_community.go @@ -0,0 +1,22 @@ +package requests + +import ( + "errors" + + "github.com/status-im/status-go/eth-node/types" +) + +var ErrRequestToJoinCommunityInvalidCommunityID = errors.New("request-to-join-community: invalid community id") + +type RequestToJoinCommunity struct { + CommunityID types.HexBytes `json:"communityId"` + ENSName string `json:"ensName"` +} + +func (j *RequestToJoinCommunity) Validate() error { + if len(j.CommunityID) == 0 { + return ErrRequestToJoinCommunityInvalidCommunityID + } + + return nil +} diff --git a/protocol/requests/share_community.go b/protocol/requests/share_community.go new file mode 100644 index 000000000..a630a70bc --- /dev/null +++ b/protocol/requests/share_community.go @@ -0,0 +1,27 @@ +package requests + +import ( + "errors" + + "github.com/status-im/status-go/eth-node/types" +) + +var ErrShareCommunityInvalidID = errors.New("share-community: invalid id") +var ErrShareCommunityEmptyUsers = errors.New("share-community: empty users") + +type ShareCommunity struct { + CommunityID types.HexBytes + Users []types.HexBytes +} + +func (j *ShareCommunity) Validate() error { + if len(j.CommunityID) == 0 { + return ErrShareCommunityInvalidID + } + + if len(j.Users) == 0 { + return ErrShareCommunityEmptyUsers + } + + return nil +} diff --git a/protocol/transport/filters_manager.go b/protocol/transport/filters_manager.go index e29b70834..a160313ee 100644 --- a/protocol/transport/filters_manager.go +++ b/protocol/transport/filters_manager.go @@ -75,9 +75,6 @@ func (s *FiltersManager) Init( chatIDs []string, publicKeys []*ecdsa.PublicKey, ) ([]*Filter, error) { - logger := s.logger.With(zap.String("site", "Init")) - - logger.Info("initializing") // Load our contact code. _, err := s.LoadContactCode(&s.privateKey.PublicKey) @@ -135,6 +132,36 @@ func (s *FiltersManager) InitPublicFilters(chatIDs []string) ([]*Filter, error) return filters, nil } +func (s *FiltersManager) InitCommunityFilters(pks []*ecdsa.PrivateKey) ([]*Filter, error) { + var filters []*Filter + s.mutex.Lock() + defer s.mutex.Unlock() + + for _, pk := range pks { + + identityStr := PublicKeyToStr(&pk.PublicKey) + rawFilter, err := s.addAsymmetric(identityStr, pk, true) + if err != nil { + return nil, err + + } + filterID := identityStr + "-admin" + filter := &Filter{ + ChatID: filterID, + FilterID: rawFilter.FilterID, + Topic: rawFilter.Topic, + Identity: identityStr, + Listen: true, + OneToOne: true, + } + + s.filters[filterID] = filter + + filters = append(filters, filter) + } + return filters, nil +} + // DEPRECATED func (s *FiltersManager) InitWithFilters(filters []*Filter) ([]*Filter, error) { var ( diff --git a/protocol/transport/transport.go b/protocol/transport/transport.go index 871e448b9..02b67119f 100644 --- a/protocol/transport/transport.go +++ b/protocol/transport/transport.go @@ -4,17 +4,18 @@ import ( "context" "crypto/ecdsa" + "github.com/status-im/status-go/eth-node/crypto" "github.com/status-im/status-go/eth-node/types" ) type Transport interface { Stop() error - JoinPrivate(publicKey *ecdsa.PublicKey) error + JoinPrivate(publicKey *ecdsa.PublicKey) (*Filter, error) LeavePrivate(publicKey *ecdsa.PublicKey) error - JoinGroup(publicKeys []*ecdsa.PublicKey) error + JoinGroup(publicKeys []*ecdsa.PublicKey) ([]*Filter, error) LeaveGroup(publicKeys []*ecdsa.PublicKey) error - JoinPublic(chatID string) error + JoinPublic(chatID string) (*Filter, error) LeavePublic(chatID string) error GetCurrentTime() uint64 MaxMessageSize() uint32 @@ -23,6 +24,7 @@ type Transport interface { SendPrivateWithSharedSecret(ctx context.Context, newMessage *types.NewMessage, publicKey *ecdsa.PublicKey, secret []byte) ([]byte, error) SendPrivateWithPartitioned(ctx context.Context, newMessage *types.NewMessage, publicKey *ecdsa.PublicKey) ([]byte, error) SendPrivateOnPersonalTopic(ctx context.Context, newMessage *types.NewMessage, publicKey *ecdsa.PublicKey) ([]byte, error) + SendCommunityMessage(ctx context.Context, newMessage *types.NewMessage, publicKey *ecdsa.PublicKey) ([]byte, error) SendMessagesRequest( ctx context.Context, peerID []byte, @@ -34,6 +36,7 @@ type Transport interface { InitFilters(chatIDs []string, publicKeys []*ecdsa.PublicKey) ([]*Filter, error) InitPublicFilters(chatIDs []string) ([]*Filter, error) + InitCommunityFilters(pks []*ecdsa.PrivateKey) ([]*Filter, error) LoadFilters(filters []*Filter) ([]*Filter, error) RemoveFilters(filters []*Filter) error RemoveFilterByChatID(string) (*Filter, error) @@ -48,3 +51,7 @@ type Transport interface { SetEnvelopeEventsHandler(handler EnvelopeEventsHandler) error } + +func PubkeyToHex(key *ecdsa.PublicKey) string { + return types.EncodeHex(crypto.FromECDSAPub(key)) +} diff --git a/protocol/transport/waku/waku_service.go b/protocol/transport/waku/waku_service.go index 821bcb21a..fe64a36a9 100644 --- a/protocol/transport/waku/waku_service.go +++ b/protocol/transport/waku/waku_service.go @@ -145,6 +145,10 @@ func (a *Transport) LoadFilters(filters []*transport.Filter) ([]*transport.Filte return a.filters.InitWithFilters(filters) } +func (a *Transport) InitCommunityFilters(pks []*ecdsa.PrivateKey) ([]*transport.Filter, error) { + return a.filters.InitCommunityFilters(pks) +} + func (a *Transport) RemoveFilters(filters []*transport.Filter) error { return a.filters.Remove(filters...) } @@ -165,9 +169,8 @@ func (a *Transport) ProcessNegotiatedSecret(secret types.NegotiatedSecret) (*tra return filter, nil } -func (a *Transport) JoinPublic(chatID string) error { - _, err := a.filters.LoadPublic(chatID) - return err +func (a *Transport) JoinPublic(chatID string) (*transport.Filter, error) { + return a.filters.LoadPublic(chatID) } func (a *Transport) LeavePublic(chatID string) error { @@ -178,13 +181,8 @@ func (a *Transport) LeavePublic(chatID string) error { return a.filters.Remove(chat) } -func (a *Transport) JoinPrivate(publicKey *ecdsa.PublicKey) error { - _, err := a.filters.LoadDiscovery() - if err != nil { - return err - } - _, err = a.filters.LoadContactCode(publicKey) - return err +func (a *Transport) JoinPrivate(publicKey *ecdsa.PublicKey) (*transport.Filter, error) { + return a.filters.LoadContactCode(publicKey) } func (a *Transport) LeavePrivate(publicKey *ecdsa.PublicKey) error { @@ -192,18 +190,17 @@ func (a *Transport) LeavePrivate(publicKey *ecdsa.PublicKey) error { return a.filters.Remove(filters...) } -func (a *Transport) JoinGroup(publicKeys []*ecdsa.PublicKey) error { - _, err := a.filters.LoadDiscovery() - if err != nil { - return err - } +func (a *Transport) JoinGroup(publicKeys []*ecdsa.PublicKey) ([]*transport.Filter, error) { + var filters []*transport.Filter for _, pk := range publicKeys { - _, err = a.filters.LoadContactCode(pk) + f, err := a.filters.LoadContactCode(pk) if err != nil { - return err + return nil, err } + filters = append(filters, f) + } - return nil + return filters, nil } func (a *Transport) LeaveGroup(publicKeys []*ecdsa.PublicKey) error { @@ -333,20 +330,22 @@ func (a *Transport) LoadKeyFilters(key *ecdsa.PrivateKey) (*transport.Filter, er return a.filters.LoadEphemeral(&key.PublicKey, key, true) } -func (a *Transport) SendPrivateOnDiscovery(ctx context.Context, newMessage *types.NewMessage, publicKey *ecdsa.PublicKey) ([]byte, error) { +func (a *Transport) SendCommunityMessage(ctx context.Context, newMessage *types.NewMessage, publicKey *ecdsa.PublicKey) ([]byte, error) { if err := a.addSig(newMessage); err != nil { return nil, err } - // There is no need to load any chat - // because listening on the discovery topic - // is done automatically. - // TODO: change this anyway, it should be explicit - // and idempotent. + // We load the filter to make sure we can post on it + filter, err := a.filters.LoadPublic(transport.PubkeyToHex(publicKey)[2:]) + if err != nil { + return nil, err + } - newMessage.Topic = types.BytesToTopic(transport.ToTopic(transport.DiscoveryTopic())) + newMessage.Topic = filter.Topic newMessage.PublicKey = crypto.FromECDSAPub(publicKey) + a.logger.Debug("SENDING message", zap.Binary("topic", filter.Topic[:])) + return a.api.Post(ctx, *newMessage) } diff --git a/protocol/transport/whisper/whisper_service.go b/protocol/transport/whisper/whisper_service.go index a8e60b2a3..ee5b3b71d 100644 --- a/protocol/transport/whisper/whisper_service.go +++ b/protocol/transport/whisper/whisper_service.go @@ -131,6 +131,10 @@ func (a *Transport) InitPublicFilters(chatIDs []string) ([]*transport.Filter, er return a.filters.InitPublicFilters(chatIDs) } +func (a *Transport) InitCommunityFilters(pks []*ecdsa.PrivateKey) ([]*transport.Filter, error) { + return a.filters.InitCommunityFilters(pks) +} + func (a *Transport) Filters() []*transport.Filter { return a.filters.Filters() } @@ -139,6 +143,23 @@ func (a *Transport) LoadFilters(filters []*transport.Filter) ([]*transport.Filte return a.filters.InitWithFilters(filters) } +func (a *Transport) SendCommunityMessage(ctx context.Context, newMessage *types.NewMessage, publicKey *ecdsa.PublicKey) ([]byte, error) { + if err := a.addSig(newMessage); err != nil { + return nil, err + } + + // We load the filter to make sure we can post on it + filter, err := a.filters.LoadPublic(transport.PubkeyToHex(publicKey)) + if err != nil { + return nil, err + } + + newMessage.Topic = filter.Topic + newMessage.PublicKey = crypto.FromECDSAPub(publicKey) + + return a.shhAPI.Post(ctx, *newMessage) +} + func (a *Transport) RemoveFilters(filters []*transport.Filter) error { return a.filters.Remove(filters...) } @@ -159,9 +180,8 @@ func (a *Transport) ProcessNegotiatedSecret(secret types.NegotiatedSecret) (*tra return filter, nil } -func (a *Transport) JoinPublic(chatID string) error { - _, err := a.filters.LoadPublic(chatID) - return err +func (a *Transport) JoinPublic(chatID string) (*transport.Filter, error) { + return a.filters.LoadPublic(chatID) } func (a *Transport) LeavePublic(chatID string) error { @@ -172,13 +192,8 @@ func (a *Transport) LeavePublic(chatID string) error { return a.filters.Remove(chat) } -func (a *Transport) JoinPrivate(publicKey *ecdsa.PublicKey) error { - _, err := a.filters.LoadDiscovery() - if err != nil { - return err - } - _, err = a.filters.LoadContactCode(publicKey) - return err +func (a *Transport) JoinPrivate(publicKey *ecdsa.PublicKey) (*transport.Filter, error) { + return a.filters.LoadContactCode(publicKey) } func (a *Transport) LeavePrivate(publicKey *ecdsa.PublicKey) error { @@ -186,18 +201,16 @@ func (a *Transport) LeavePrivate(publicKey *ecdsa.PublicKey) error { return a.filters.Remove(filters...) } -func (a *Transport) JoinGroup(publicKeys []*ecdsa.PublicKey) error { - _, err := a.filters.LoadDiscovery() - if err != nil { - return err - } +func (a *Transport) JoinGroup(publicKeys []*ecdsa.PublicKey) ([]*transport.Filter, error) { + var filters []*transport.Filter for _, pk := range publicKeys { - _, err = a.filters.LoadContactCode(pk) + f, err := a.filters.LoadContactCode(pk) if err != nil { - return err + return nil, err } + filters = append(filters, f) } - return nil + return filters, nil } func (a *Transport) LeaveGroup(publicKeys []*ecdsa.PublicKey) error { diff --git a/protocol/v1/status_message.go b/protocol/v1/status_message.go index 4a35dc271..75691b85b 100644 --- a/protocol/v1/status_message.go +++ b/protocol/v1/status_message.go @@ -225,6 +225,8 @@ func (m *StatusMessage) HandleApplication() error { return m.unmarshalProtobufData(new(protobuf.CommunityDescription)) case protobuf.ApplicationMetadataMessage_COMMUNITY_INVITATION: return m.unmarshalProtobufData(new(protobuf.CommunityInvitation)) + case protobuf.ApplicationMetadataMessage_COMMUNITY_REQUEST_TO_JOIN: + return m.unmarshalProtobufData(new(protobuf.CommunityRequestToJoin)) case protobuf.ApplicationMetadataMessage_PUSH_NOTIFICATION_REGISTRATION: // This message is a bit different as it's encrypted, so we pass it straight through v := reflect.ValueOf(m.UnwrappedPayload) diff --git a/services/ext/api.go b/services/ext/api.go index b0958d507..3ab4e7cc5 100644 --- a/services/ext/api.go +++ b/services/ext/api.go @@ -21,6 +21,7 @@ import ( "github.com/status-im/status-go/protocol/encryption/multidevice" "github.com/status-im/status-go/protocol/protobuf" "github.com/status-im/status-go/protocol/pushnotificationclient" + "github.com/status-im/status-go/protocol/requests" "github.com/status-im/status-go/protocol/transport" "github.com/status-im/status-go/protocol/urls" "github.com/status-im/status-go/services/ext/mailservers" @@ -200,10 +201,6 @@ func (api *PublicAPI) ConfirmMessagesProcessedByID(messageConfirmations []*Metad return api.service.ConfirmMessagesProcessed(encryptionIDs) } -func (api *PublicAPI) Join(chat protocol.Chat) error { - return api.service.messenger.Join(chat) -} - func (api *PublicAPI) Leave(chat protocol.Chat) error { return api.service.messenger.Leave(chat) } @@ -260,6 +257,10 @@ func (api *PublicAPI) SaveChat(parent context.Context, chat *protocol.Chat) erro return api.service.messenger.SaveChat(chat) } +func (api *PublicAPI) CreateOneToOneChat(parent context.Context, request *requests.CreateOneToOneChat) (*protocol.MessengerResponse, error) { + return api.service.messenger.CreateOneToOneChat(request) +} + func (api *PublicAPI) Chats(parent context.Context) []*protocol.Chat { return api.service.messenger.Chats() } @@ -328,28 +329,28 @@ func (api *PublicAPI) JoinedCommunities(parent context.Context) ([]*communities. } // JoinCommunity joins a community with the given ID -func (api *PublicAPI) JoinCommunity(parent context.Context, communityID string) (*protocol.MessengerResponse, error) { +func (api *PublicAPI) JoinCommunity(parent context.Context, communityID types.HexBytes) (*protocol.MessengerResponse, error) { return api.service.messenger.JoinCommunity(communityID) } // LeaveCommunity leaves a commuity with the given ID -func (api *PublicAPI) LeaveCommunity(parent context.Context, communityID string) (*protocol.MessengerResponse, error) { +func (api *PublicAPI) LeaveCommunity(parent context.Context, communityID types.HexBytes) (*protocol.MessengerResponse, error) { return api.service.messenger.LeaveCommunity(communityID) } // CreateCommunity creates a new community with the provided description -func (api *PublicAPI) CreateCommunity(description *protobuf.CommunityDescription) (*protocol.MessengerResponse, error) { - return api.service.messenger.CreateCommunity(description) +func (api *PublicAPI) CreateCommunity(request *requests.CreateCommunity) (*protocol.MessengerResponse, error) { + return api.service.messenger.CreateCommunity(request) } // ExportCommunity exports the private key of the community with given ID -func (api *PublicAPI) ExportCommunity(id string) (string, error) { +func (api *PublicAPI) ExportCommunity(id types.HexBytes) (types.HexBytes, error) { key, err := api.service.messenger.ExportCommunity(id) if err != nil { - return "", err + return nil, err } - return types.EncodeHex(crypto.FromECDSA(key)), nil + return crypto.FromECDSA(key), nil } // ImportCommunity imports a community with the given private key in hex @@ -364,18 +365,48 @@ func (api *PublicAPI) ImportCommunity(hexPrivateKey string) (*protocol.Messenger } // CreateCommunityChat creates a community chat in the given community -func (api *PublicAPI) CreateCommunityChat(orgID string, c *protobuf.CommunityChat) (*protocol.MessengerResponse, error) { - return api.service.messenger.CreateCommunityChat(orgID, c) +func (api *PublicAPI) CreateCommunityChat(communityID types.HexBytes, c *protobuf.CommunityChat) (*protocol.MessengerResponse, error) { + return api.service.messenger.CreateCommunityChat(communityID, c) } -// InviteUserToCommunity invites the user with pk to the community with ID -func (api *PublicAPI) InviteUserToCommunity(orgID, userPublicKey string) (*protocol.MessengerResponse, error) { - return api.service.messenger.InviteUserToCommunity(orgID, userPublicKey) +// InviteUsersToCommunity invites the users with pks to the community with ID +func (api *PublicAPI) InviteUsersToCommunity(request *requests.InviteUsersToCommunity) (*protocol.MessengerResponse, error) { + return api.service.messenger.InviteUsersToCommunity(request) +} + +// ShareCommunity share the community with a set of users +func (api *PublicAPI) ShareCommunity(request *requests.ShareCommunity) (*protocol.MessengerResponse, error) { + return api.service.messenger.ShareCommunity(request) } // RemoveUserFromCommunity removes the user with pk from the community with ID -func (api *PublicAPI) RemoveUserFromCommunity(orgID, userPublicKey string) (*protocol.MessengerResponse, error) { - return api.service.messenger.RemoveUserFromCommunity(orgID, userPublicKey) +func (api *PublicAPI) RemoveUserFromCommunity(communityID types.HexBytes, userPublicKey string) (*protocol.MessengerResponse, error) { + return api.service.messenger.RemoveUserFromCommunity(communityID, userPublicKey) +} + +// MyPendingRequestsToJoin returns the pending requests for the logged in user +func (api *PublicAPI) MyPendingRequestsToJoin() ([]*communities.RequestToJoin, error) { + return api.service.messenger.MyPendingRequestsToJoin() +} + +// PendingRequestsToJoinForCommunity returns the pending requests to join for a given community +func (api *PublicAPI) PendingRequestsToJoinForCommunity(id types.HexBytes) ([]*communities.RequestToJoin, error) { + return api.service.messenger.PendingRequestsToJoinForCommunity(id) +} + +// AcceptRequestToJoinCommunity accepts a pending request to join a community +func (api *PublicAPI) AcceptRequestToJoinCommunity(request *requests.AcceptRequestToJoinCommunity) (*protocol.MessengerResponse, error) { + return api.service.messenger.AcceptRequestToJoinCommunity(request) +} + +// DeclineRequestToJoinCommunity accepts a pending request to join a community +func (api *PublicAPI) DeclineRequestToJoinCommunity(request *requests.DeclineRequestToJoinCommunity) error { + return api.service.messenger.DeclineRequestToJoinCommunity(request) +} + +// RequestToJoinCommunity requests to join a particular community +func (api *PublicAPI) RequestToJoinCommunity(request *requests.RequestToJoinCommunity) (*protocol.MessengerResponse, error) { + return api.service.messenger.RequestToJoinCommunity(request) } type ApplicationMessagesResponse struct { @@ -656,6 +687,10 @@ func (api *PublicAPI) GetLinkPreviewData(link string) (previewData urls.LinkPrev return urls.GetLinkPreviewData(link) } +func (api *PublicAPI) EnsVerified(pk, ensName string) error { + return api.service.messenger.ENSVerified(pk, ensName) +} + // Echo is a method for testing purposes. func (api *PublicAPI) Echo(ctx context.Context, message string) (string, error) { return message, nil diff --git a/services/ext/service.go b/services/ext/service.go index 56ccdddcd..b049c3cad 100644 --- a/services/ext/service.go +++ b/services/ext/service.go @@ -180,10 +180,18 @@ func (s *Service) StartMessenger() (*protocol.MessengerResponse, error) { } go s.retrieveMessagesLoop(time.Second, s.cancelMessenger) go s.verifyTransactionLoop(30*time.Second, s.cancelMessenger) - go s.verifyENSLoop(30*time.Second, s.cancelMessenger) return response, nil } +func publishMessengerResponse(response *protocol.MessengerResponse) { + if !response.IsEmpty() { + PublisherSignalHandler{}.NewMessages(response) + localnotifications.SendMessageNotifications(response.Notifications) + // Clear notifications as not used for now + response.Notifications = nil + } +} + func (s *Service) retrieveMessagesLoop(tick time.Duration, cancel <-chan struct{}) { ticker := time.NewTicker(tick) defer ticker.Stop() @@ -196,10 +204,7 @@ func (s *Service) retrieveMessagesLoop(tick time.Duration, cancel <-chan struct{ log.Error("failed to retrieve raw messages", "err", err) continue } - if !response.IsEmpty() { - PublisherSignalHandler{}.NewMessages(response) - localnotifications.SendMessageNotifications(response.Notifications) - } + publishMessengerResponse(response) case <-cancel: return } @@ -270,35 +275,6 @@ func (c *verifyTransactionClient) TransactionByHash(ctx context.Context, hash ty return coremessage, coretypes.TransactionStatus(receipt.Status), nil } -func (s *Service) verifyENSLoop(tick time.Duration, cancel <-chan struct{}) { - if s.config.VerifyENSURL == "" || s.config.VerifyENSContractAddress == "" { - log.Warn("not starting ENS loop") - return - } - - ticker := time.NewTicker(tick) - defer ticker.Stop() - - ctx, cancelVerifyENS := context.WithCancel(context.Background()) - - for { - select { - case <-ticker.C: - response, err := s.messenger.VerifyENSNames(ctx, s.config.VerifyENSURL, s.config.VerifyENSContractAddress) - if err != nil { - log.Error("failed to validate ens", "err", err) - continue - } - if !response.IsEmpty() { - PublisherSignalHandler{}.NewMessages(response) - } - case <-cancel: - cancelVerifyENS() - return - } - } -} - func (s *Service) verifyTransactionLoop(tick time.Duration, cancel <-chan struct{}) { if s.config.VerifyTransactionURL == "" { log.Warn("not starting transaction loop") @@ -329,10 +305,8 @@ func (s *Service) verifyTransactionLoop(tick time.Duration, cancel <-chan struct log.Error("failed to validate transactions", "err", err) continue } - if !response.IsEmpty() { - PublisherSignalHandler{}.NewMessages(response) - localnotifications.SendMessageNotifications(response.Notifications) - } + publishMessengerResponse(response) + case <-cancel: cancelVerifyTransaction() return @@ -478,11 +452,14 @@ func buildMessengerOptions( protocol.WithAccount(account), protocol.WithEnvelopesMonitorConfig(envelopesMonitorConfig), protocol.WithOnNegotiatedFilters(onNegotiatedFilters), - protocol.WithDeliveredHandler(messageDeliveredHandler)} + protocol.WithDeliveredHandler(messageDeliveredHandler), + protocol.WithENSVerificationConfig(publishMessengerResponse, config.VerifyENSURL, config.VerifyENSContractAddress), + } if config.DataSyncEnabled { options = append(options, protocol.WithDatasync()) } + settings, err := accountsDB.GetSettings() if err != sql.ErrNoRows && err != nil { return nil, err