github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/bind/notifications.go (about)

     1  package keybase
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"runtime"
     7  	"time"
     8  
     9  	"github.com/keybase/client/go/chat"
    10  	"github.com/keybase/client/go/chat/globals"
    11  	"github.com/keybase/client/go/chat/storage"
    12  	"github.com/keybase/client/go/chat/types"
    13  	"github.com/keybase/client/go/chat/utils"
    14  	"github.com/keybase/client/go/libkb"
    15  	"github.com/keybase/client/go/protocol/chat1"
    16  	"github.com/keybase/client/go/protocol/gregor1"
    17  	"github.com/keybase/client/go/protocol/keybase1"
    18  	"github.com/kyokomi/emoji"
    19  	context "golang.org/x/net/context"
    20  )
    21  
    22  type Person struct {
    23  	KeybaseUsername string
    24  	KeybaseAvatar   string
    25  	IsBot           bool
    26  }
    27  
    28  type Message struct {
    29  	ID            int
    30  	Kind          string // "Text" | "Reaction"
    31  	Plaintext     string
    32  	ServerMessage string // This is the server's suggested display message for the notification
    33  	From          *Person
    34  	At            int64
    35  }
    36  
    37  type ChatNotification struct {
    38  	Message   *Message
    39  	ConvID    string
    40  	TeamName  string
    41  	TopicName string
    42  	TlfName   string
    43  	// e.g. "keybase#general, CoolTeam, Susannah,Jake"
    44  	ConversationName    string
    45  	IsGroupConversation bool
    46  	IsPlaintext         bool
    47  	SoundName           string
    48  	BadgeCount          int
    49  }
    50  
    51  func HandlePostTextReply(strConvID, tlfName string, intMessageID int, body string) (err error) {
    52  	ctx := context.Background()
    53  	defer kbCtx.CTrace(ctx, "HandlePostTextReply", &err)()
    54  	defer func() { err = flattenError(err) }()
    55  	outboxID, err := storage.NewOutboxID()
    56  	if err != nil {
    57  		return err
    58  	}
    59  	convID, err := chat1.MakeConvID(strConvID)
    60  	if err != nil {
    61  		return err
    62  	}
    63  	_, err = kbCtx.ChatHelper.SendTextByIDNonblock(context.Background(), convID, tlfName, body, &outboxID, nil)
    64  
    65  	kbCtx.Log.CDebugf(ctx, "Marking as read from QuickReply: convID: %s", strConvID)
    66  	gc := globals.NewContext(kbCtx, kbChatCtx)
    67  	uid, err := utils.AssertLoggedInUID(ctx, gc)
    68  	if err != nil {
    69  		return err
    70  	}
    71  
    72  	msgID := chat1.MessageID(intMessageID)
    73  	if err = kbChatCtx.InboxSource.MarkAsRead(context.Background(), convID, uid, &msgID, false /* forceUnread */); err != nil {
    74  		kbCtx.Log.CDebugf(ctx, "Failed to mark as read from QuickReply: convID: %s. Err: %s", strConvID, err)
    75  		// We don't want to fail this method call just because we couldn't mark it as aread
    76  		err = nil
    77  	}
    78  
    79  	return nil
    80  }
    81  
    82  func HandleBackgroundNotification(strConvID, body, serverMessageBody, sender string, intMembersType int,
    83  	displayPlaintext bool, intMessageID int, pushID string, badgeCount, unixTime int, soundName string,
    84  	pusher PushNotifier, showIfStale bool) (err error) {
    85  	if err := waitForInit(10 * time.Second); err != nil {
    86  		return err
    87  	}
    88  	gc := globals.NewContext(kbCtx, kbChatCtx)
    89  	ctx := globals.ChatCtx(context.Background(), gc,
    90  		keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, chat.NewCachingIdentifyNotifier(gc))
    91  
    92  	defer kbCtx.CTrace(ctx, fmt.Sprintf("HandleBackgroundNotification(%s,%s,%v,%d,%d,%s,%d,%d)",
    93  		strConvID, sender, displayPlaintext, intMembersType, intMessageID, pushID, badgeCount, unixTime), &err)()
    94  	defer func() { err = flattenError(err) }()
    95  
    96  	// Unbox
    97  	if !kbCtx.ActiveDevice.HaveKeys() {
    98  		return libkb.LoginRequiredError{}
    99  	}
   100  	mp := chat.NewMobilePush(gc)
   101  	uid := gregor1.UID(kbCtx.Env.GetUID().ToBytes())
   102  	convID, err := chat1.MakeConvID(strConvID)
   103  	if err != nil {
   104  		kbCtx.Log.CDebugf(ctx, "HandleBackgroundNotification: invalid convID: %s msg: %s", strConvID, err)
   105  		return err
   106  	}
   107  	membersType := chat1.ConversationMembersType(intMembersType)
   108  	conv, err := utils.GetVerifiedConv(ctx, gc, uid, convID, types.InboxSourceDataSourceLocalOnly)
   109  	if err != nil {
   110  		kbCtx.Log.CDebugf(ctx, "Failed to get conversation info", err)
   111  		return err
   112  	}
   113  
   114  	chatNotification := ChatNotification{
   115  		IsPlaintext: displayPlaintext,
   116  		Message: &Message{
   117  			ID:            intMessageID,
   118  			ServerMessage: serverMessageBody,
   119  			From:          &Person{},
   120  			At:            int64(unixTime) * 1000,
   121  		},
   122  		ConvID:              strConvID,
   123  		TopicName:           conv.Info.TopicName,
   124  		TlfName:             conv.Info.TlfName,
   125  		IsGroupConversation: len(conv.Info.Participants) > 2,
   126  		ConversationName:    utils.FormatConversationName(conv.Info, string(kbCtx.Env.GetUsername())),
   127  		SoundName:           soundName,
   128  		BadgeCount:          badgeCount,
   129  	}
   130  
   131  	msgUnboxed, err := mp.UnboxPushNotification(ctx, uid, convID, membersType, body)
   132  	if err == nil && msgUnboxed.IsValid() {
   133  		chatNotification.Message.From.IsBot = msgUnboxed.SenderIsBot()
   134  		username := msgUnboxed.Valid().SenderUsername
   135  		chatNotification.Message.From.KeybaseUsername = username
   136  
   137  		if displayPlaintext && !msgUnboxed.Valid().IsEphemeral() {
   138  			// We show avatars on Android
   139  			if runtime.GOOS == "android" {
   140  				avatar, err := kbSvc.GetUserAvatar(username)
   141  
   142  				if err != nil {
   143  					kbCtx.Log.CDebugf(ctx, "Push Notif: Err in getting user avatar %v", err)
   144  				} else {
   145  					chatNotification.Message.From.KeybaseAvatar = avatar
   146  				}
   147  			}
   148  
   149  			switch msgUnboxed.GetMessageType() {
   150  			case chat1.MessageType_TEXT:
   151  				chatNotification.Message.Kind = "Text"
   152  				chatNotification.Message.Plaintext = msgUnboxed.Valid().MessageBody.Text().Body
   153  			case chat1.MessageType_REACTION:
   154  				chatNotification.Message.Kind = "Reaction"
   155  				reaction, err := utils.GetReaction(msgUnboxed)
   156  				if err != nil {
   157  					return err
   158  				}
   159  				chatNotification.Message.Plaintext =
   160  					emoji.Sprintf("Reacted to your message with %v", reaction)
   161  			default:
   162  				kbCtx.Log.CDebugf(ctx, "unboxNotification: Unknown message type: %v",
   163  					msgUnboxed.GetMessageType())
   164  				return errors.New("invalid message type for plaintext")
   165  			}
   166  		}
   167  	} else {
   168  		kbCtx.Log.CDebugf(ctx, "unboxNotification: failed to unbox: %s", err)
   169  		chatNotification.Message.From.KeybaseUsername = sender
   170  		// just bail out of here at this point since we won't be displaying anything useful,
   171  		// and we don't want to accidentally ack the plaintext notification when we didn't really
   172  		// display it.
   173  		if len(serverMessageBody) == 0 {
   174  			return errors.New("Unbox failed; nothing to display")
   175  		}
   176  	}
   177  
   178  	age := time.Since(time.Unix(int64(unixTime), 0))
   179  
   180  	// On iOS we don't want to show stale notifications. Nonsilent notifications
   181  	// can come later and cause duplicate notifications. On Android, both silent
   182  	// and non-silent notifications go through this function; and Java checks if we
   183  	// have already seen a notification. We don't need this stale logic.
   184  	if !showIfStale && age >= 2*time.Minute {
   185  		kbCtx.Log.CDebugf(ctx, "HandleBackgroundNotification: stale notification: %v", age)
   186  		return errors.New("stale notification")
   187  	}
   188  
   189  	// only display and ack this notification if we actually have something to display
   190  	if pusher != nil && (len(chatNotification.Message.Plaintext) > 0 || len(chatNotification.Message.ServerMessage) > 0) {
   191  		pusher.DisplayChatNotification(&chatNotification)
   192  		if len(pushID) > 0 {
   193  			mp.AckNotificationSuccess(ctx, []string{pushID})
   194  		}
   195  	}
   196  	return nil
   197  }