Request/Decline access to communities

This commit is contained in:
Andrea Maria Piana 2021-01-11 11:32:51 +01:00
parent 99a304686f
commit f115b8d289
60 changed files with 3868 additions and 1672 deletions

View File

@ -1 +1 @@
0.71.7
0.72.0

View File

@ -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
}

View File

@ -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,

View File

@ -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 {

View File

@ -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

View File

@ -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
}
func (o *Community) MarshalJSON() ([]byte, error) {
item := struct {
*protobuf.CommunityDescription `json:"description"`
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) {
if o.config.MemberIdentity == nil {
return nil, errors.New("member identity not set")
}
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.IDString(),
CommunityDescription: o.config.CommunityDescription,
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(),
}
return json.Marshal(item)
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),
}
}

View File

@ -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 {

View File

@ -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")

View File

@ -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
ensSubscription chan []*ens.VerificationRecord
subscriptions []chan *Subscription
ensVerifier *ens.Verifier
identity *ecdsa.PublicKey
logger *zap.Logger
quit chan struct{}
}
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")
}
func NewManager(db *sql.DB, logger *zap.Logger) (*Manager, error) {
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{
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
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
return m.inviteUsersToCommunity(community, pks)
}
err = m.persistence.SaveCommunity(community)
if err != nil {
return nil, err
}
m.publish(&Subscription{Community: community, Invitation: invitation})
return community, nil
}
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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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))
}

View File

@ -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 := &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 := &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",
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 := &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 := &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)
}

View File

@ -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 {

View File

@ -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
}

8
protocol/ens/const.go Normal file
View File

@ -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

105
protocol/ens/persistence.go Normal file
View File

@ -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
}

View File

@ -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)
}

25
protocol/ens/record.go Normal file
View File

@ -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
}

View File

@ -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)
}

194
protocol/ens/verifier.go Normal file
View File

@ -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
}

View File

@ -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)
})
}
}

View File

@ -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)
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

View File

@ -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
}
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
contact.ENSVerified = record.Verified
contact.Name = record.Name
contacts = append(contacts, contact)
}
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
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
}
}
payload, err := proto.Marshal(invitation)
if err != nil {
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)
}
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)

216
protocol/messenger_chats.go Normal file
View File

@ -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")
}
}

View File

@ -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
}

View File

@ -21,6 +21,7 @@ type config struct {
// 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)
onContactENSVerified func(*MessengerResponse)
// systemMessagesTranslations holds translations for system-messages
systemMessagesTranslations map[protobuf.MembershipUpdateEvent_EventType]string
@ -38,6 +39,8 @@ type config struct {
account *multiaccounts.Account
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
}
}

View File

@ -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())

View File

@ -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)

View File

@ -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)

View File

@ -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)
}

View File

@ -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)

View File

@ -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))

View File

@ -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,6 +11,28 @@ import (
)
type MessengerResponse struct {
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
chats map[string]*Chat
removedChats map[string]bool
communities map[string]*communities.Community
}
func (r *MessengerResponse) MarshalJSON() ([]byte, error) {
responseItem := struct {
Chats []*Chat `json:"chats,omitempty"`
RemovedChats []string `json:"removedChats,omitempty"`
Messages []*common.Message `json:"messages,omitempty"`
@ -16,63 +40,195 @@ type MessengerResponse struct {
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"`
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,
}
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
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
}

View File

@ -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) {

View File

@ -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,14 +639,14 @@ 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))
@ -655,12 +655,11 @@ func (s *MessengerSuite) TestRetrieveBlockedContact() {
Name: "contact-name",
LastUpdated: 20,
SystemTags: []string{contactBlocked},
TributeToTalk: "talk",
}
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

View File

@ -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
}
@ -706,36 +727,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,
"1614152139_add_communities_request_to_join.up.sql": _1614152139_add_communities_request_to_joinUpSql,
"README.md": readmeMd,
"doc.go": docGo,
}
// AssetDebug is true if the assets were built with the debug flag enabled.
const AssetDebug = false
// AssetDir returns the file names below a certain
// directory embedded in the file by go-bindata.
// For example if you run go-bindata on data/... and data contains the
@ -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.

View File

@ -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;

View File

@ -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

View File

@ -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)

View File

@ -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,
}

View File

@ -39,5 +39,6 @@ message ApplicationMetadataMessage {
CHAT_IDENTITY = 24;
COMMUNITY_DESCRIPTION = 25;
COMMUNITY_INVITATION = 26;
COMMUNITY_REQUEST_TO_JOIN = 27;
}
}

View File

@ -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,6 +143,7 @@ func (m *Grant) GetClock() uint64 {
}
type CommunityMember struct {
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:"-"`
@ -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,
}

View File

@ -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;
}

View File

@ -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)

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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 (

View File

@ -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))
}

View File

@ -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)
}

View File

@ -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 {

View File

@ -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)

View File

@ -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

View File

@ -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