package protocol import ( "context" "crypto/ecdsa" "encoding/hex" "errors" "testing" "github.com/stretchr/testify/suite" "go.uber.org/zap" gethbridge "github.com/status-im/status-go/eth-node/bridge/geth" "github.com/status-im/status-go/eth-node/crypto" "github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/protocol/common" "github.com/status-im/status-go/protocol/communities" "github.com/status-im/status-go/protocol/protobuf" "github.com/status-im/status-go/protocol/pushnotificationclient" "github.com/status-im/status-go/protocol/pushnotificationserver" "github.com/status-im/status-go/protocol/requests" "github.com/status-im/status-go/protocol/tt" "github.com/status-im/status-go/waku" ) const ( bob1DeviceToken = "token-1" bob2DeviceToken = "token-2" testAPNTopic = "topic" ) func TestMessengerPushNotificationSuite(t *testing.T) { suite.Run(t, new(MessengerPushNotificationSuite)) } type MessengerPushNotificationSuite struct { suite.Suite m *Messenger // main instance of Messenger privateKey *ecdsa.PrivateKey // private key for the main instance of Messenger // If one wants to send messages between different instances of Messenger, // a single Waku service should be shared. shh types.Waku logger *zap.Logger } func (s *MessengerPushNotificationSuite) SetupTest() { s.logger = tt.MustCreateTestLogger() config := waku.DefaultConfig config.MinimumAcceptedPoW = 0 shh := waku.New(&config, s.logger) s.shh = gethbridge.NewGethWakuWrapper(shh) s.Require().NoError(shh.Start()) s.m = s.newMessenger(s.shh) s.privateKey = s.m.identity } func (s *MessengerPushNotificationSuite) TearDownTest() { TearDownMessenger(&s.Suite, s.m) _ = s.logger.Sync() } func (s *MessengerPushNotificationSuite) newMessenger(shh types.Waku) *Messenger { privateKey, err := crypto.GenerateKey() s.Require().NoError(err) messenger, err := newMessengerWithKey(s.shh, privateKey, s.logger, []Option{WithPushNotifications()}) s.Require().NoError(err) return messenger } func (s *MessengerPushNotificationSuite) newPushNotificationServer(shh types.Waku, privateKey *ecdsa.PrivateKey) *Messenger { serverConfig := &pushnotificationserver.Config{ Enabled: true, Logger: s.logger, Identity: privateKey, } options := []Option{ WithPushNotificationServerConfig(serverConfig), } messenger, err := newMessengerWithKey(shh, privateKey, s.logger, options) s.Require().NoError(err) return messenger } func (s *MessengerPushNotificationSuite) TestReceivePushNotification() { bob1 := s.m bob2, err := newMessengerWithKey(s.shh, s.m.identity, s.logger, []Option{WithPushNotifications()}) s.Require().NoError(err) defer TearDownMessenger(&s.Suite, bob2) serverKey, err := crypto.GenerateKey() s.Require().NoError(err) server := s.newPushNotificationServer(s.shh, serverKey) defer TearDownMessenger(&s.Suite, server) alice := s.newMessenger(s.shh) defer TearDownMessenger(&s.Suite, alice) s.Require().NoError(alice.EnableSendingPushNotifications()) bobInstallationIDs := []string{bob1.installationID, bob2.installationID} // Register bob1 err = bob1.AddPushNotificationsServer(context.Background(), &server.identity.PublicKey, pushnotificationclient.ServerTypeCustom) s.Require().NoError(err) err = bob1.RegisterForPushNotifications(context.Background(), bob1DeviceToken, testAPNTopic, protobuf.PushNotificationRegistration_APN_TOKEN) // Pull servers and check we registered err = tt.RetryWithBackOff(func() error { _, err = server.RetrieveAll() if err != nil { return err } _, err = bob1.RetrieveAll() if err != nil { return err } registered, err := bob1.RegisteredForPushNotifications() if err != nil { return err } if !registered { return errors.New("not registered") } bobServers, err := bob1.GetPushNotificationsServers() if err != nil { return err } if len(bobServers) == 0 { return errors.New("not registered") } return nil }) // Make sure we receive it s.Require().NoError(err) bob1Servers, err := bob1.GetPushNotificationsServers() s.Require().NoError(err) // Register bob2 err = bob2.AddPushNotificationsServer(context.Background(), &server.identity.PublicKey, pushnotificationclient.ServerTypeCustom) s.Require().NoError(err) err = bob2.RegisterForPushNotifications(context.Background(), bob2DeviceToken, testAPNTopic, protobuf.PushNotificationRegistration_APN_TOKEN) s.Require().NoError(err) err = tt.RetryWithBackOff(func() error { _, err = server.RetrieveAll() if err != nil { return err } _, err = bob2.RetrieveAll() if err != nil { return err } registered, err := bob2.RegisteredForPushNotifications() if err != nil { return err } if !registered { return errors.New("not registered") } bobServers, err := bob2.GetPushNotificationsServers() if err != nil { return err } if len(bobServers) == 0 { return errors.New("not registered") } return nil }) // Make sure we receive it s.Require().NoError(err) bob2Servers, err := bob2.GetPushNotificationsServers() s.Require().NoError(err) // 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) response, err := alice.SendChatMessage(context.Background(), inputMessage) s.Require().NoError(err) messageIDString := response.Messages()[0].ID messageID, err := hex.DecodeString(messageIDString[2:]) s.Require().NoError(err) infoMap := make(map[string]*pushnotificationclient.PushNotificationInfo) err = tt.RetryWithBackOff(func() error { _, err = server.RetrieveAll() if err != nil { return err } _, err = alice.RetrieveAll() if err != nil { return err } info, err := alice.pushNotificationClient.GetPushNotificationInfo(&bob1.identity.PublicKey, bobInstallationIDs) if err != nil { return err } for _, i := range info { infoMap[i.AccessToken] = i } // Check we have replies for both bob1 and bob2 if len(infoMap) != 2 { return errors.New("info not fetched") } return nil }) s.Require().Len(infoMap, 2) // Check we have replies for both bob1 and bob2 var bob1Info, bob2Info *pushnotificationclient.PushNotificationInfo bob1Info = infoMap[bob1Servers[0].AccessToken] bob2Info = infoMap[bob2Servers[0].AccessToken] s.Require().NotNil(bob1Info) s.Require().Equal(bob1.installationID, bob1Info.InstallationID) s.Require().Equal(bob1Servers[0].AccessToken, bob1Info.AccessToken) s.Require().Equal(&bob1.identity.PublicKey, bob1Info.PublicKey) s.Require().NotNil(bob2Info) s.Require().Equal(bob2.installationID, bob2Info.InstallationID) s.Require().Equal(bob2Servers[0].AccessToken, bob2Info.AccessToken) s.Require().Equal(&bob2.identity.PublicKey, bob2Info.PublicKey) retrievedNotificationInfo, err := alice.pushNotificationClient.GetPushNotificationInfo(&bob1.identity.PublicKey, bobInstallationIDs) s.Require().NoError(err) s.Require().NotNil(retrievedNotificationInfo) s.Require().Len(retrievedNotificationInfo, 2) var sentNotification *pushnotificationclient.SentNotification err = tt.RetryWithBackOff(func() error { _, err = server.RetrieveAll() if err != nil { return err } _, err = alice.RetrieveAll() if err != nil { return err } sentNotification, err = alice.pushNotificationClient.GetSentNotification(common.HashPublicKey(&bob1.identity.PublicKey), bob1.installationID, messageID) if err != nil { return err } if sentNotification == nil { return errors.New("sent notification not found") } if !sentNotification.Success { return errors.New("sent notification not successul") } return nil }) s.Require().NoError(err) } func (s *MessengerPushNotificationSuite) TestReceivePushNotificationFromContactOnly() { bob := s.m serverKey, err := crypto.GenerateKey() s.Require().NoError(err) server := s.newPushNotificationServer(s.shh, serverKey) defer TearDownMessenger(&s.Suite, server) alice := s.newMessenger(s.shh) defer TearDownMessenger(&s.Suite, alice) s.Require().NoError(alice.EnableSendingPushNotifications()) bobInstallationIDs := []string{bob.installationID} // Register bob err = bob.AddPushNotificationsServer(context.Background(), &server.identity.PublicKey, pushnotificationclient.ServerTypeCustom) s.Require().NoError(err) // Add alice has a contact aliceContact := &Contact{ ID: types.EncodeHex(crypto.FromECDSAPub(&alice.identity.PublicKey)), EnsName: "Some Contact", ContactRequestLocalState: ContactRequestStateSent, } _, err = bob.AddContact(context.Background(), &requests.AddContact{ID: aliceContact.ID}) s.Require().NoError(err) // Enable from contacts only err = bob.EnablePushNotificationsFromContactsOnly() s.Require().NoError(err) err = bob.RegisterForPushNotifications(context.Background(), bob1DeviceToken, testAPNTopic, protobuf.PushNotificationRegistration_APN_TOKEN) s.Require().NoError(err) // Pull servers and check we registered err = tt.RetryWithBackOff(func() error { _, err = server.RetrieveAll() if err != nil { return err } _, err = bob.RetrieveAll() if err != nil { return err } registered, err := bob.RegisteredForPushNotifications() if err != nil { return err } if !registered { return errors.New("not registered") } bobServers, err := bob.GetPushNotificationsServers() if err != nil { return err } if len(bobServers) == 0 { return errors.New("not registered") } return nil }) // Make sure we receive it s.Require().NoError(err) bobServers, err := bob.GetPushNotificationsServers() s.Require().NoError(err) // 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) response, err := alice.SendChatMessage(context.Background(), inputMessage) s.Require().NoError(err) messageIDString := response.Messages()[0].ID messageID, err := hex.DecodeString(messageIDString[2:]) s.Require().NoError(err) var info []*pushnotificationclient.PushNotificationInfo err = tt.RetryWithBackOff(func() error { _, err = server.RetrieveAll() if err != nil { return err } _, err = alice.RetrieveAll() if err != nil { return err } info, err = alice.pushNotificationClient.GetPushNotificationInfo(&bob.identity.PublicKey, bobInstallationIDs) if err != nil { return err } // Check we have replies for bob if len(info) != 1 { return errors.New("info not fetched") } return nil }) s.Require().NoError(err) s.Require().NotNil(info) s.Require().Equal(bob.installationID, info[0].InstallationID) s.Require().Equal(bobServers[0].AccessToken, info[0].AccessToken) s.Require().Equal(&bob.identity.PublicKey, info[0].PublicKey) retrievedNotificationInfo, err := alice.pushNotificationClient.GetPushNotificationInfo(&bob.identity.PublicKey, bobInstallationIDs) s.Require().NoError(err) s.Require().NotNil(retrievedNotificationInfo) s.Require().Len(retrievedNotificationInfo, 1) var sentNotification *pushnotificationclient.SentNotification err = tt.RetryWithBackOff(func() error { _, err = server.RetrieveAll() if err != nil { return err } _, err = alice.RetrieveAll() if err != nil { return err } sentNotification, err = alice.pushNotificationClient.GetSentNotification(common.HashPublicKey(&bob.identity.PublicKey), bob.installationID, messageID) if err != nil { return err } if sentNotification == nil { return errors.New("sent notification not found") } if !sentNotification.Success { return errors.New("sent notification not successul") } return nil }) s.Require().NoError(err) } func (s *MessengerPushNotificationSuite) TestReceivePushNotificationRetries() { bob := s.m serverKey, err := crypto.GenerateKey() s.Require().NoError(err) server := s.newPushNotificationServer(s.shh, serverKey) defer TearDownMessenger(&s.Suite, server) alice := s.newMessenger(s.shh) // another contact to invalidate the token frank := s.newMessenger(s.shh) defer TearDownMessenger(&s.Suite, frank) defer TearDownMessenger(&s.Suite, alice) s.Require().NoError(alice.EnableSendingPushNotifications()) bobInstallationIDs := []string{bob.installationID} // Register bob err = bob.AddPushNotificationsServer(context.Background(), &server.identity.PublicKey, pushnotificationclient.ServerTypeCustom) s.Require().NoError(err) // Add alice has a contact aliceContact := &Contact{ ID: types.EncodeHex(crypto.FromECDSAPub(&alice.identity.PublicKey)), EnsName: "Some Contact", ContactRequestLocalState: ContactRequestStateSent, } _, err = bob.AddContact(context.Background(), &requests.AddContact{ID: aliceContact.ID}) s.Require().NoError(err) // Add frank has a contact frankContact := &Contact{ ID: types.EncodeHex(crypto.FromECDSAPub(&frank.identity.PublicKey)), EnsName: "Some Contact", ContactRequestLocalState: ContactRequestStateSent, } _, err = bob.AddContact(context.Background(), &requests.AddContact{ID: frankContact.ID}) s.Require().NoError(err) // Enable from contacts only err = bob.EnablePushNotificationsFromContactsOnly() s.Require().NoError(err) err = bob.RegisterForPushNotifications(context.Background(), bob1DeviceToken, testAPNTopic, protobuf.PushNotificationRegistration_APN_TOKEN) s.Require().NoError(err) // Pull servers and check we registered err = tt.RetryWithBackOff(func() error { _, err = server.RetrieveAll() if err != nil { return err } _, err = bob.RetrieveAll() if err != nil { return err } registered, err := bob.RegisteredForPushNotifications() if err != nil { return err } if !registered { return errors.New("not registered") } bobServers, err := bob.GetPushNotificationsServers() if err != nil { return err } if len(bobServers) == 0 { return errors.New("not registered") } return nil }) // Make sure we receive it s.Require().NoError(err) bobServers, err := bob.GetPushNotificationsServers() s.Require().NoError(err) // 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) _, err = alice.SendChatMessage(context.Background(), inputMessage) s.Require().NoError(err) // We check that alice retrieves the info from the server var info []*pushnotificationclient.PushNotificationInfo err = tt.RetryWithBackOff(func() error { _, err = server.RetrieveAll() if err != nil { return err } _, err = alice.RetrieveAll() if err != nil { return err } info, err = alice.pushNotificationClient.GetPushNotificationInfo(&bob.identity.PublicKey, bobInstallationIDs) if err != nil { return err } // Check we have replies for bob if len(info) != 1 { return errors.New("info not fetched") } return nil }) s.Require().NoError(err) s.Require().NotNil(info) s.Require().Equal(bob.installationID, info[0].InstallationID) s.Require().Equal(bobServers[0].AccessToken, info[0].AccessToken) s.Require().Equal(&bob.identity.PublicKey, info[0].PublicKey) // The message has been sent, but not received, now we remove a contact so that the token is invalidated frankContact = &Contact{ ID: types.EncodeHex(crypto.FromECDSAPub(&frank.identity.PublicKey)), EnsName: "Some Contact", } _, err = bob.RemoveContact(context.Background(), frankContact.ID) s.Require().NoError(err) // Re-registration should be triggered, pull from server and bob to check we are correctly registered // Pull servers and check we registered err = tt.RetryWithBackOff(func() error { _, err = server.RetrieveAll() if err != nil { return err } _, err = bob.RetrieveAll() if err != nil { return err } registered, err := bob.RegisteredForPushNotifications() if err != nil { return err } if !registered { return errors.New("not registered") } return nil }) newBobServers, err := bob.GetPushNotificationsServers() s.Require().NoError(err) // Make sure access token is not the same s.Require().NotEqual(newBobServers[0].AccessToken, bobServers[0].AccessToken) // Send another message, here the token will not be valid inputMessage = buildTestMessage(*chat) response, err := alice.SendChatMessage(context.Background(), inputMessage) s.Require().NoError(err) messageIDString := response.Messages()[0].ID messageID, err := hex.DecodeString(messageIDString[2:]) s.Require().NoError(err) err = tt.RetryWithBackOff(func() error { _, err = server.RetrieveAll() if err != nil { return err } _, err = alice.RetrieveAll() if err != nil { return err } info, err = alice.pushNotificationClient.GetPushNotificationInfo(&bob.identity.PublicKey, bobInstallationIDs) if err != nil { return err } // Check we have replies for bob if len(info) != 1 { return errors.New("info not fetched") } if newBobServers[0].AccessToken != info[0].AccessToken { return errors.New("still using the old access token") } return nil }) s.Require().NoError(err) s.Require().NotNil(info) s.Require().Equal(bob.installationID, info[0].InstallationID) s.Require().Equal(newBobServers[0].AccessToken, info[0].AccessToken) s.Require().Equal(&bob.identity.PublicKey, info[0].PublicKey) retrievedNotificationInfo, err := alice.pushNotificationClient.GetPushNotificationInfo(&bob.identity.PublicKey, bobInstallationIDs) s.Require().NoError(err) s.Require().NotNil(retrievedNotificationInfo) s.Require().Len(retrievedNotificationInfo, 1) var sentNotification *pushnotificationclient.SentNotification err = tt.RetryWithBackOff(func() error { _, err = server.RetrieveAll() if err != nil { return err } _, err = alice.RetrieveAll() if err != nil { return err } sentNotification, err = alice.pushNotificationClient.GetSentNotification(common.HashPublicKey(&bob.identity.PublicKey), bob.installationID, messageID) if err != nil { return err } if sentNotification == nil { return errors.New("sent notification not found") } if !sentNotification.Success { return errors.New("sent notification not successul") } return nil }) s.Require().NoError(err) } func (s *MessengerPushNotificationSuite) TestContactCode() { bob1 := s.m serverKey, err := crypto.GenerateKey() s.Require().NoError(err) server := s.newPushNotificationServer(s.shh, serverKey) defer TearDownMessenger(&s.Suite, server) alice := s.newMessenger(s.shh) s.Require().NoError(err) defer TearDownMessenger(&s.Suite, alice) s.Require().NoError(alice.EnableSendingPushNotifications()) // Register bob1 err = bob1.AddPushNotificationsServer(context.Background(), &server.identity.PublicKey, pushnotificationclient.ServerTypeCustom) s.Require().NoError(err) err = bob1.RegisterForPushNotifications(context.Background(), bob1DeviceToken, testAPNTopic, protobuf.PushNotificationRegistration_APN_TOKEN) // Pull servers and check we registered err = tt.RetryWithBackOff(func() error { _, err = server.RetrieveAll() if err != nil { return err } _, err = bob1.RetrieveAll() if err != nil { return err } registered, err := bob1.RegisteredForPushNotifications() if err != nil { return err } if !registered { return errors.New("not registered") } bobServers, err := bob1.GetPushNotificationsServers() if err != nil { return err } if len(bobServers) == 0 { return errors.New("not registered") } return nil }) // Make sure we receive it s.Require().NoError(err) contactCodeAdvertisement, err := bob1.buildContactCodeAdvertisement() s.Require().NoError(err) s.Require().NotNil(contactCodeAdvertisement) s.Require().NoError(alice.pushNotificationClient.HandleContactCodeAdvertisement(&bob1.identity.PublicKey, contactCodeAdvertisement)) } func (s *MessengerPushNotificationSuite) TestReceivePushNotificationMention() { bob := s.m serverKey, err := crypto.GenerateKey() s.Require().NoError(err) server := s.newPushNotificationServer(s.shh, serverKey) defer TearDownMessenger(&s.Suite, server) alice := s.newMessenger(s.shh) s.Require().NoError(err) defer TearDownMessenger(&s.Suite, alice) s.Require().NoError(alice.EnableSendingPushNotifications()) bobInstallationIDs := []string{bob.installationID} // Create public chat and join for both alice and bob chat := CreatePublicChat("status", s.m.transport) err = bob.SaveChat(chat) s.Require().NoError(err) _, err = bob.Join(chat) s.Require().NoError(err) err = alice.SaveChat(chat) s.Require().NoError(err) _, err = alice.Join(chat) s.Require().NoError(err) // Register bob err = bob.AddPushNotificationsServer(context.Background(), &server.identity.PublicKey, pushnotificationclient.ServerTypeCustom) s.Require().NoError(err) err = bob.RegisterForPushNotifications(context.Background(), bob1DeviceToken, testAPNTopic, protobuf.PushNotificationRegistration_APN_TOKEN) // Pull servers and check we registered err = tt.RetryWithBackOff(func() error { _, err = server.RetrieveAll() if err != nil { return err } _, err = bob.RetrieveAll() if err != nil { return err } registered, err := bob.RegisteredForPushNotifications() if err != nil { return err } if !registered { return errors.New("not registered") } bobServers, err := bob.GetPushNotificationsServers() if err != nil { return err } if len(bobServers) == 0 { return errors.New("not registered") } return nil }) // Make sure we receive it s.Require().NoError(err) bobServers, err := bob.GetPushNotificationsServers() s.Require().NoError(err) inputMessage := buildTestMessage(*chat) // message contains a mention inputMessage.Text = "Hey @" + types.EncodeHex(crypto.FromECDSAPub(&bob.identity.PublicKey)) response, err := alice.SendChatMessage(context.Background(), inputMessage) s.Require().NoError(err) messageIDString := response.Messages()[0].ID messageID, err := hex.DecodeString(messageIDString[2:]) s.Require().NoError(err) var bobInfo []*pushnotificationclient.PushNotificationInfo err = tt.RetryWithBackOff(func() error { _, err = server.RetrieveAll() if err != nil { return err } _, err = alice.RetrieveAll() if err != nil { return err } bobInfo, err = alice.pushNotificationClient.GetPushNotificationInfo(&bob.identity.PublicKey, bobInstallationIDs) if err != nil { return err } // Check we have replies for bob if len(bobInfo) != 1 { return errors.New("info not fetched") } return nil }) s.Require().NoError(err) s.Require().NotEmpty(bobInfo) s.Require().Equal(bob.installationID, bobInfo[0].InstallationID) s.Require().Equal(bobServers[0].AccessToken, bobInfo[0].AccessToken) s.Require().Equal(&bob.identity.PublicKey, bobInfo[0].PublicKey) retrievedNotificationInfo, err := alice.pushNotificationClient.GetPushNotificationInfo(&bob.identity.PublicKey, bobInstallationIDs) s.Require().NoError(err) s.Require().NotNil(retrievedNotificationInfo) s.Require().Len(retrievedNotificationInfo, 1) var sentNotification *pushnotificationclient.SentNotification err = tt.RetryWithBackOff(func() error { _, err = server.RetrieveAll() if err != nil { return err } _, err = alice.RetrieveAll() if err != nil { return err } sentNotification, err = alice.pushNotificationClient.GetSentNotification(common.HashPublicKey(&bob.identity.PublicKey), bob.installationID, messageID) if err != nil { return err } if sentNotification == nil { return errors.New("sent notification not found") } if !sentNotification.Success { return errors.New("sent notification not successul") } return nil }) s.Require().NoError(err) } func (s *MessengerPushNotificationSuite) TestReceivePushNotificationCommunityRequest() { bob := s.m serverKey, err := crypto.GenerateKey() s.Require().NoError(err) server := s.newPushNotificationServer(s.shh, serverKey) defer TearDownMessenger(&s.Suite, server) alice := s.newMessenger(s.shh) s.Require().NoError(err) defer TearDownMessenger(&s.Suite, alice) s.Require().NoError(alice.EnableSendingPushNotifications()) // Register bob err = bob.AddPushNotificationsServer(context.Background(), &server.identity.PublicKey, pushnotificationclient.ServerTypeCustom) s.Require().NoError(err) err = bob.RegisterForPushNotifications(context.Background(), bob1DeviceToken, testAPNTopic, protobuf.PushNotificationRegistration_APN_TOKEN) // Pull servers and check we registered err = tt.RetryWithBackOff(func() error { _, err = server.RetrieveAll() if err != nil { return err } _, err = bob.RetrieveAll() if err != nil { return err } registered, err := bob.RegisteredForPushNotifications() if err != nil { return err } if !registered { return errors.New("not registered") } bobServers, err := bob.GetPushNotificationsServers() if err != nil { return err } if len(bobServers) == 0 { return errors.New("not registered") } return nil }) // Make sure we receive it s.Require().NoError(err) _, err = bob.GetPushNotificationsServers() s.Require().NoError(err) description := &requests.CreateCommunity{ Membership: protobuf.CommunityPermissions_MANUAL_ACCEPT, Name: "status", Color: "#ffffff", Description: "status community description", } response, err := bob.CreateCommunity(description, true) s.Require().NoError(err) s.Require().NotNil(response) s.Require().Len(response.Communities(), 1) community := response.Communities()[0] // Send a community message chat := CreateOneToOneChat(common.PubkeyToHex(&alice.identity.PublicKey), &alice.identity.PublicKey, alice.transport) inputMessage := common.NewMessage() inputMessage.ChatId = chat.ID inputMessage.Text = "some text" inputMessage.CommunityID = community.IDString() err = bob.SaveChat(chat) s.NoError(err) _, err = bob.SendChatMessage(context.Background(), inputMessage) s.NoError(err) // Pull message and make sure org is received err = tt.RetryWithBackOff(func() error { response, err = alice.RetrieveAll() if err != nil { return err } if len(response.Communities()) == 0 { return errors.New("community not received") } return nil }) request := &requests.RequestToJoinCommunity{CommunityID: community.ID()} // We try to join the org response, err = alice.RequestToJoinCommunity(request) s.Require().NoError(err) s.Require().NotNil(response) s.Require().Len(response.RequestsToJoinCommunity(), 1) requestToJoin1 := response.RequestsToJoinCommunity()[0] s.Require().NotNil(requestToJoin1) s.Require().Equal(community.ID(), requestToJoin1.CommunityID) s.Require().True(requestToJoin1.Our) s.Require().NotEmpty(requestToJoin1.ID) s.Require().NotEmpty(requestToJoin1.Clock) s.Require().Equal(requestToJoin1.PublicKey, common.PubkeyToHex(&alice.identity.PublicKey)) s.Require().Equal(communities.RequestToJoinStatePending, requestToJoin1.State) err = tt.RetryWithBackOff(func() error { _, err = server.RetrieveAll() if err != nil { return err } _, err = alice.RetrieveAll() if err != nil { return err } if server.pushNotificationServer.SentRequests != 1 { return errors.New("request not sent") } return nil }) s.Require().NoError(err) } func (s *MessengerPushNotificationSuite) TestReceivePushNotificationPairedDevices() { bob1 := s.m bob2, err := newMessengerWithKey(s.shh, s.m.identity, s.logger, []Option{WithPushNotifications()}) s.Require().NoError(err) defer TearDownMessenger(&s.Suite, bob2) serverKey, err := crypto.GenerateKey() s.Require().NoError(err) server := s.newPushNotificationServer(s.shh, serverKey) defer TearDownMessenger(&s.Suite, server) alice := s.newMessenger(s.shh) s.Require().NoError(err) defer TearDownMessenger(&s.Suite, alice) s.Require().NoError(alice.EnableSendingPushNotifications()) bobInstallationIDs := []string{bob1.installationID, bob2.installationID} // Register bob1 err = bob1.AddPushNotificationsServer(context.Background(), &server.identity.PublicKey, pushnotificationclient.ServerTypeCustom) s.Require().NoError(err) err = bob1.RegisterForPushNotifications(context.Background(), bob1DeviceToken, testAPNTopic, protobuf.PushNotificationRegistration_APN_TOKEN) // Pull servers and check we registered err = tt.RetryWithBackOff(func() error { _, err = server.RetrieveAll() if err != nil { return err } _, err = bob1.RetrieveAll() if err != nil { return err } registered, err := bob1.RegisteredForPushNotifications() if err != nil { return err } if !registered { return errors.New("not registered") } bobServers, err := bob1.GetPushNotificationsServers() if err != nil { return err } if len(bobServers) == 0 { return errors.New("not registered") } return nil }) // Make sure we receive it s.Require().NoError(err) bob1Servers, err := bob1.GetPushNotificationsServers() s.Require().NoError(err) // Register bob2 err = bob2.AddPushNotificationsServer(context.Background(), &server.identity.PublicKey, pushnotificationclient.ServerTypeCustom) s.Require().NoError(err) err = bob2.RegisterForPushNotifications(context.Background(), bob2DeviceToken, testAPNTopic, protobuf.PushNotificationRegistration_APN_TOKEN) s.Require().NoError(err) err = tt.RetryWithBackOff(func() error { _, err = server.RetrieveAll() if err != nil { return err } _, err = bob2.RetrieveAll() if err != nil { return err } registered, err := bob2.RegisteredForPushNotifications() if err != nil { return err } if !registered { return errors.New("not registered") } bobServers, err := bob2.GetPushNotificationsServers() if err != nil { return err } if len(bobServers) == 0 { return errors.New("not registered") } return nil }) // Make sure we receive it s.Require().NoError(err) bob2Servers, err := bob2.GetPushNotificationsServers() s.Require().NoError(err) // 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) response, err := alice.SendChatMessage(context.Background(), inputMessage) s.Require().NoError(err) messageIDString := response.Messages()[0].ID messageID, err := hex.DecodeString(messageIDString[2:]) s.Require().NoError(err) infoMap := make(map[string]*pushnotificationclient.PushNotificationInfo) err = tt.RetryWithBackOff(func() error { _, err = server.RetrieveAll() if err != nil { return err } _, err = alice.RetrieveAll() if err != nil { return err } info, err := alice.pushNotificationClient.GetPushNotificationInfo(&bob1.identity.PublicKey, bobInstallationIDs) if err != nil { return err } for _, i := range info { infoMap[i.AccessToken] = i } // Check we have replies for both bob1 and bob2 if len(infoMap) != 2 { return errors.New("info not fetched") } return nil }) s.Require().Len(infoMap, 2) // Check we have replies for both bob1 and bob2 var bob1Info, bob2Info *pushnotificationclient.PushNotificationInfo bob1Info = infoMap[bob1Servers[0].AccessToken] bob2Info = infoMap[bob2Servers[0].AccessToken] s.Require().NotNil(bob1Info) s.Require().Equal(bob1.installationID, bob1Info.InstallationID) s.Require().Equal(bob1Servers[0].AccessToken, bob1Info.AccessToken) s.Require().Equal(&bob1.identity.PublicKey, bob1Info.PublicKey) s.Require().NotNil(bob2Info) s.Require().Equal(bob2.installationID, bob2Info.InstallationID) s.Require().Equal(bob2Servers[0].AccessToken, bob2Info.AccessToken) s.Require().Equal(&bob2.identity.PublicKey, bob2Info.PublicKey) retrievedNotificationInfo, err := alice.pushNotificationClient.GetPushNotificationInfo(&bob1.identity.PublicKey, bobInstallationIDs) s.Require().NoError(err) s.Require().NotNil(retrievedNotificationInfo) s.Require().Len(retrievedNotificationInfo, 2) var sentNotification *pushnotificationclient.SentNotification err = tt.RetryWithBackOff(func() error { _, err = server.RetrieveAll() if err != nil { return err } _, err = alice.RetrieveAll() if err != nil { return err } sentNotification, err = alice.pushNotificationClient.GetSentNotification(common.HashPublicKey(&bob1.identity.PublicKey), bob1.installationID, messageID) if err != nil { return err } if sentNotification == nil { return errors.New("sent notification not found") } if !sentNotification.Success { return errors.New("sent notification not successul") } return nil }) s.Require().NoError(err) } func (s *MessengerPushNotificationSuite) TestReceivePushNotificationReply() { bob := s.m serverKey, err := crypto.GenerateKey() s.Require().NoError(err) server := s.newPushNotificationServer(s.shh, serverKey) defer TearDownMessenger(&s.Suite, server) alice := s.newMessenger(s.shh) s.Require().NoError(err) defer TearDownMessenger(&s.Suite, alice) s.Require().NoError(alice.EnableSendingPushNotifications()) bobInstallationIDs := []string{bob.installationID} // Create public chat and join for both alice and bob chat := CreatePublicChat("status", s.m.transport) err = bob.SaveChat(chat) s.Require().NoError(err) _, err = bob.Join(chat) s.Require().NoError(err) err = alice.SaveChat(chat) s.Require().NoError(err) _, err = alice.Join(chat) s.Require().NoError(err) // Register bob err = bob.AddPushNotificationsServer(context.Background(), &server.identity.PublicKey, pushnotificationclient.ServerTypeCustom) s.Require().NoError(err) err = bob.RegisterForPushNotifications(context.Background(), bob1DeviceToken, testAPNTopic, protobuf.PushNotificationRegistration_APN_TOKEN) // Pull servers and check we registered err = tt.RetryWithBackOff(func() error { _, err = server.RetrieveAll() if err != nil { return err } _, err = bob.RetrieveAll() if err != nil { return err } registered, err := bob.RegisteredForPushNotifications() if err != nil { return err } if !registered { return errors.New("not registered") } bobServers, err := bob.GetPushNotificationsServers() if err != nil { return err } if len(bobServers) == 0 { return errors.New("not registered") } return nil }) // Make sure we receive it s.Require().NoError(err) bobServers, err := bob.GetPushNotificationsServers() s.Require().NoError(err) firstMessage := buildTestMessage(*chat) firstMessage.Text = "Hello!" response, err := bob.SendChatMessage(context.Background(), firstMessage) s.Require().NoError(err) messageIDString := response.Messages()[0].ID _, err = WaitOnMessengerResponse( alice, func(r *MessengerResponse) bool { for _, message := range r.Messages() { if message.ID == messageIDString { return true } } return false }, "no messages", ) replyMessage := buildTestMessage(*chat) replyMessage.Text = "Hello reply" replyMessage.ResponseTo = messageIDString response, err = alice.SendChatMessage(context.Background(), replyMessage) s.Require().NoError(err) messageIDString = response.Messages()[0].ID messageID, err := hex.DecodeString(messageIDString[2:]) s.Require().NoError(err) var bobInfo []*pushnotificationclient.PushNotificationInfo err = tt.RetryWithBackOff(func() error { _, err = server.RetrieveAll() if err != nil { return err } _, err = alice.RetrieveAll() if err != nil { return err } bobInfo, err = alice.pushNotificationClient.GetPushNotificationInfo(&bob.identity.PublicKey, bobInstallationIDs) if err != nil { return err } // Check we have replies for bob if len(bobInfo) != 1 { return errors.New("info not fetched") } return nil }) s.Require().NoError(err) s.Require().NotEmpty(bobInfo) s.Require().Equal(bob.installationID, bobInfo[0].InstallationID) s.Require().Equal(bobServers[0].AccessToken, bobInfo[0].AccessToken) s.Require().Equal(&bob.identity.PublicKey, bobInfo[0].PublicKey) retrievedNotificationInfo, err := alice.pushNotificationClient.GetPushNotificationInfo(&bob.identity.PublicKey, bobInstallationIDs) s.Require().NoError(err) s.Require().NotNil(retrievedNotificationInfo) s.Require().Len(retrievedNotificationInfo, 1) var sentNotification *pushnotificationclient.SentNotification err = tt.RetryWithBackOff(func() error { _, err = server.RetrieveAll() if err != nil { return err } _, err = alice.RetrieveAll() if err != nil { return err } sentNotification, err = alice.pushNotificationClient.GetSentNotification(common.HashPublicKey(&bob.identity.PublicKey), bob.installationID, messageID) if err != nil { return err } if sentNotification == nil { return errors.New("sent notification not found") } if !sentNotification.Success { return errors.New("sent notification not successul") } return nil }) s.Require().NoError(err) }