Skip to content

Commit b3dd20c

Browse files
committed
chat/v2: send two way chat notifications.
Also refactor a bit of the notifier system to not cause a circular dependency.
1 parent 8f2c655 commit b3dd20c

File tree

10 files changed

+249
-61
lines changed

10 files changed

+249
-61
lines changed

pkg/code/chat/chat.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const (
88
KinPurchasesName = "Kin Purchases"
99
PaymentsName = "Payments" // Renamed to Web Payments on client
1010
TipsName = "Tips"
11+
TwoWayChatName = "Two Way Chat"
1112

1213
// Test chats used for unit/integration testing only
1314
TestCantMuteName = "TestCantMute"
@@ -45,6 +46,11 @@ var (
4546
CanMute: true,
4647
CanUnsubscribe: false,
4748
},
49+
TwoWayChatName: {
50+
TitleLocalizationKey: localization.ChatTitleTwoWay,
51+
CanMute: true,
52+
CanUnsubscribe: false,
53+
},
4854

4955
TestCantMuteName: {
5056
TitleLocalizationKey: "n/a",

pkg/code/chat/message_tips.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,14 @@ import (
1818
chat_v1 "github.com/code-payments/code-server/pkg/code/data/chat/v1"
1919
chat_v2 "github.com/code-payments/code-server/pkg/code/data/chat/v2"
2020
"github.com/code-payments/code-server/pkg/code/data/intent"
21-
chat_server "github.com/code-payments/code-server/pkg/code/server/grpc/chat/v2"
2221
)
2322

2423
// SendTipsExchangeMessage sends a message to the Tips chat with exchange data
2524
// content related to the submitted intent. Intents that don't belong in the
2625
// Tips chat will be ignored.
2726
//
2827
// Note: Tests covered in SubmitIntent history tests
29-
func SendTipsExchangeMessage(ctx context.Context, data code_data.Provider, notifier chat_server.Notifier, intentRecord *intent.Record) ([]*MessageWithOwner, error) {
28+
func SendTipsExchangeMessage(ctx context.Context, data code_data.Provider, notifier Notifier, intentRecord *intent.Record) ([]*MessageWithOwner, error) {
3029
intentIdRaw, err := base58.Decode(intentRecord.IntentId)
3130
if err != nil {
3231
return nil, fmt.Errorf("invalid intent id: %w", err)
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package chat_v2
1+
package chat
22

33
import (
44
"context"

pkg/code/chat/sender.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import (
1717
code_data "github.com/code-payments/code-server/pkg/code/data"
1818
chat_v1 "github.com/code-payments/code-server/pkg/code/data/chat/v1"
1919
chat_v2 "github.com/code-payments/code-server/pkg/code/data/chat/v2"
20-
chatserver "github.com/code-payments/code-server/pkg/code/server/grpc/chat/v2"
2120
)
2221

2322
// SendNotificationChatMessageV1 sends a chat message to a receiving owner account.
@@ -124,7 +123,7 @@ func SendNotificationChatMessageV1(
124123
func SendNotificationChatMessageV2(
125124
ctx context.Context,
126125
data code_data.Provider,
127-
notifier chatserver.Notifier,
126+
notifier Notifier,
128127
chatTitle string,
129128
isVerifiedChat bool,
130129
receiver *common.Account,

pkg/code/data/chat/v2/model.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ type MemberRecord struct {
7070
Platform Platform
7171
PlatformId string
7272

73+
// If Platform != PlatformCode, this store the owner
74+
// of the account (at time of creation). This allows
75+
// us to send push notifications for non-code users.
76+
OwnerAccount string
77+
7378
DeliveryPointer *MessageId
7479
ReadPointer *MessageId
7580

@@ -79,6 +84,14 @@ type MemberRecord struct {
7984
JoinedAt time.Time
8085
}
8186

87+
func (m *MemberRecord) GetOwner() string {
88+
if m.Platform == PlatformCode {
89+
return m.PlatformId
90+
}
91+
92+
return m.OwnerAccount
93+
}
94+
8295
type MessageRecord struct {
8396
Id int64
8497
ChatId ChatId
@@ -292,6 +305,9 @@ func (r *MemberRecord) Validate() error {
292305
if len(r.PlatformId) == 0 {
293306
return errors.New("platform id is required")
294307
}
308+
if r.Platform != PlatformCode && len(r.OwnerAccount) == 0 {
309+
return errors.New("owner account is required for non code platform members")
310+
}
295311

296312
switch r.Platform {
297313
case PlatformCode:
@@ -349,8 +365,9 @@ func (r *MemberRecord) Clone() MemberRecord {
349365
ChatId: r.ChatId,
350366
MemberId: r.MemberId,
351367

352-
Platform: r.Platform,
353-
PlatformId: r.PlatformId,
368+
Platform: r.Platform,
369+
PlatformId: r.PlatformId,
370+
OwnerAccount: r.OwnerAccount,
354371

355372
DeliveryPointer: deliveryPointerCopy,
356373
ReadPointer: readPointerCopy,
@@ -370,6 +387,7 @@ func (r *MemberRecord) CopyTo(dst *MemberRecord) {
370387

371388
dst.Platform = r.Platform
372389
dst.PlatformId = r.PlatformId
390+
dst.OwnerAccount = r.OwnerAccount
373391

374392
if r.DeliveryPointer != nil {
375393
cloned := r.DeliveryPointer.Clone()

pkg/code/localization/keys.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ const (
4343
ChatTitleKinPurchases = "title.chat.kinPurchases"
4444
ChatTitlePayments = "title.chat.payments"
4545
ChatTitleTips = "title.chat.tips"
46+
ChatTitleTwoWay = "title.chat.twoWay"
4647

4748
// Message Bodies
4849

pkg/code/push/notifications.go

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"google.golang.org/protobuf/proto"
1010

1111
chatpb "github.com/code-payments/code-protobuf-api/generated/go/chat/v1"
12+
chatv2pb "github.com/code-payments/code-protobuf-api/generated/go/chat/v2"
1213
commonpb "github.com/code-payments/code-protobuf-api/generated/go/common/v1"
1314

1415
chat_util "github.com/code-payments/code-server/pkg/code/chat"
@@ -412,3 +413,147 @@ func SendChatMessagePushNotification(
412413
}
413414
return nil
414415
}
416+
417+
func SendChatMessagePushNotificationV2(
418+
ctx context.Context,
419+
data code_data.Provider,
420+
pusher push_lib.Provider,
421+
chatTitle string,
422+
owner *common.Account,
423+
chatMessage *chatv2pb.ChatMessage,
424+
) error {
425+
log := logrus.StandardLogger().WithFields(logrus.Fields{
426+
"method": "SendChatMessagePushNotificationV2",
427+
"owner": owner.PublicKey().ToBase58(),
428+
"chat": chatTitle,
429+
})
430+
431+
// Best-effort try to update the badge count before pushing message content
432+
//
433+
// Note: Only chat messages generate badge counts
434+
err := UpdateBadgeCount(ctx, data, pusher, owner)
435+
if err != nil {
436+
log.WithError(err).Warn("failure updating badge count on device")
437+
}
438+
439+
locale, err := data.GetUserLocale(ctx, owner.PublicKey().ToBase58())
440+
if err != nil {
441+
log.WithError(err).Warn("failure getting user locale")
442+
return err
443+
}
444+
445+
var localizedPushTitle string
446+
447+
chatProperties, ok := chat_util.InternalChatProperties[chatTitle]
448+
if ok {
449+
localized, err := localization.Localize(locale, chatProperties.TitleLocalizationKey)
450+
if err != nil {
451+
return nil
452+
}
453+
localizedPushTitle = localized
454+
} else {
455+
domainDisplayName, err := thirdparty.GetDomainDisplayName(chatTitle)
456+
if err == nil {
457+
localizedPushTitle = domainDisplayName
458+
} else {
459+
return nil
460+
}
461+
}
462+
463+
var anyErrorPushingContent bool
464+
for _, content := range chatMessage.Content {
465+
var contentToPush *chatv2pb.Content
466+
switch typedContent := content.Type.(type) {
467+
case *chatv2pb.Content_Localized:
468+
localizedPushBody, err := localization.Localize(locale, typedContent.Localized.KeyOrText)
469+
if err != nil {
470+
continue
471+
}
472+
473+
contentToPush = &chatv2pb.Content{
474+
Type: &chatv2pb.Content_Localized{
475+
Localized: &chatv2pb.LocalizedContent{
476+
KeyOrText: localizedPushBody,
477+
},
478+
},
479+
}
480+
case *chatv2pb.Content_ExchangeData:
481+
var currencyCode currency_lib.Code
482+
var nativeAmount float64
483+
if typedContent.ExchangeData.GetExact() != nil {
484+
exchangeData := typedContent.ExchangeData.GetExact()
485+
currencyCode = currency_lib.Code(exchangeData.Currency)
486+
nativeAmount = exchangeData.NativeAmount
487+
} else {
488+
exchangeData := typedContent.ExchangeData.GetPartial()
489+
currencyCode = currency_lib.Code(exchangeData.Currency)
490+
nativeAmount = exchangeData.NativeAmount
491+
}
492+
493+
localizedPushBody, err := localization.LocalizeFiatWithVerb(
494+
locale,
495+
chatpb.ExchangeDataContent_Verb(typedContent.ExchangeData.Verb),
496+
currencyCode,
497+
nativeAmount,
498+
true,
499+
)
500+
if err != nil {
501+
continue
502+
}
503+
504+
contentToPush = &chatv2pb.Content{
505+
Type: &chatv2pb.Content_Localized{
506+
Localized: &chatv2pb.LocalizedContent{
507+
KeyOrText: localizedPushBody,
508+
},
509+
},
510+
}
511+
case *chatv2pb.Content_NaclBox, *chatv2pb.Content_Text:
512+
contentToPush = content
513+
case *chatv2pb.Content_ThankYou:
514+
contentToPush = &chatv2pb.Content{
515+
Type: &chatv2pb.Content_Localized{
516+
Localized: &chatv2pb.LocalizedContent{
517+
// todo: localize this
518+
KeyOrText: "🙏 They thanked you for their tip",
519+
},
520+
},
521+
}
522+
}
523+
524+
if contentToPush == nil {
525+
continue
526+
}
527+
528+
marshalledContent, err := proto.Marshal(contentToPush)
529+
if err != nil {
530+
log.WithError(err).Warn("failure marshalling chat content")
531+
return err
532+
}
533+
534+
kvs := map[string]string{
535+
"chat_title": localizedPushTitle,
536+
"message_content": base64.StdEncoding.EncodeToString(marshalledContent),
537+
}
538+
539+
err = sendMutableNotificationToOwner(
540+
ctx,
541+
data,
542+
pusher,
543+
owner,
544+
chatMessageDataPush,
545+
chatTitle,
546+
kvs,
547+
)
548+
if err != nil {
549+
anyErrorPushingContent = true
550+
log.WithError(err).Warn("failure sending data push notification")
551+
}
552+
}
553+
554+
if anyErrorPushingContent {
555+
return errors.New("at least one piece of content failed to push")
556+
}
557+
558+
return nil
559+
}

0 commit comments

Comments
 (0)