diff --git a/go/chat/localizer.go b/go/chat/localizer.go index af3aa42fb48b..444624b75473 100644 --- a/go/chat/localizer.go +++ b/go/chat/localizer.go @@ -845,8 +845,9 @@ func (s *localizerPipeline) localizeConversation(ctx context.Context, uid gregor var maxValidID chat1.MessageID s.Debug(ctx, "localizing %d max msgs", len(maxMsgs)) for _, mm := range maxMsgs { - if mm.IsValid() && - utils.IsSnippetChatMessageType(mm.GetMessageType()) && + isValidSnippet := mm.IsValid() && utils.IsSnippetChatMessageType(mm.GetMessageType()) + isEphemeralErr := mm.IsError() && mm.Error().IsEphemeral + if (isValidSnippet || isEphemeralErr) && (conversationLocal.Info.SnippetMsg == nil || conversationLocal.Info.SnippetMsg.GetMessageID() < mm.GetMessageID()) { conversationLocal.Info.SnippetMsg = new(chat1.MessageUnboxed) diff --git a/go/chat/utils/utils.go b/go/chat/utils/utils.go index e65d478aa612..36e1042b8723 100644 --- a/go/chat/utils/utils.go +++ b/go/chat/utils/utils.go @@ -1116,10 +1116,13 @@ func formatDuration(dur time.Duration) string { func getMsgSnippetDecoration(msg chat1.MessageUnboxed) chat1.SnippetDecoration { var msgBody chat1.MessageBody - if msg.IsValid() { + switch { + case msg.IsValid(): msgBody = msg.Valid().MessageBody - } else { + case msg.IsOutbox(): msgBody = msg.Outbox().Msg.MessageBody + default: + return chat1.SnippetDecoration_NONE } switch msg.GetMessageType() { case chat1.MessageType_ATTACHMENT: @@ -1220,14 +1223,20 @@ func GetMsgSnippetBody(ctx context.Context, g *globals.Context, uid gregor1.UID, func GetMsgSnippet(ctx context.Context, g *globals.Context, uid gregor1.UID, msg chat1.MessageUnboxed, conv chat1.ConversationLocal, currentUsername string, ) (decoration chat1.SnippetDecoration, snippet string, snippetDecorated string) { - if !msg.IsValid() && !msg.IsOutbox() { - return chat1.SnippetDecoration_NONE, "", "" - } defer func() { if len(snippetDecorated) == 0 { snippetDecorated = snippet } }() + if !msg.IsValid() && !msg.IsOutbox() { + if msg.IsError() && msg.Error().IsEphemeral { + if msg.Error().IsEphemeralExpired(time.Now()) { + return chat1.SnippetDecoration_EXPLODED_MESSAGE, "Message exploded.", "" + } + return chat1.SnippetDecoration_EXPLODING_MESSAGE, msg.Error().ErrMsg, "" + } + return chat1.SnippetDecoration_NONE, "", "" + } var senderUsername string if msg.IsValid() { diff --git a/go/chat/utils/utils_test.go b/go/chat/utils/utils_test.go index a8b0acdabc0c..e39b1ac63060 100644 --- a/go/chat/utils/utils_test.go +++ b/go/chat/utils/utils_test.go @@ -1086,6 +1086,36 @@ func TestSearchableRemoteConversationName(t *testing.T) { searchableRemoteConversationNameFromStr("joshblum,zoommikem,mikem,zoomua,mikem", "mikem")) } +func TestGetMsgSnippetEphemeralError(t *testing.T) { + ctx := context.Background() + conv := chat1.ConversationLocal{} + errMsg := "This exploding message is not available because this device was created after the message was sent" + + // Non-expired ephemeral error: should surface the error message with EXPLODING_MESSAGE decoration. + msg := chat1.NewMessageUnboxedWithError(chat1.MessageUnboxedError{ + ErrType: chat1.MessageUnboxedErrorType_EPHEMERAL, + ErrMsg: errMsg, + IsEphemeral: true, + Etime: gregor1.ToTime(time.Now().Add(time.Hour)), + MessageType: chat1.MessageType_TEXT, + }) + decoration, snippet, _ := GetMsgSnippet(ctx, nil, gregor1.UID{}, msg, conv, "alice") + require.Equal(t, chat1.SnippetDecoration_EXPLODING_MESSAGE, decoration) + require.Equal(t, errMsg, snippet) + + // Expired ephemeral error: should show "Message exploded." with EXPLODED_MESSAGE decoration. + msg = chat1.NewMessageUnboxedWithError(chat1.MessageUnboxedError{ + ErrType: chat1.MessageUnboxedErrorType_EPHEMERAL, + ErrMsg: errMsg, + IsEphemeral: true, + Etime: gregor1.ToTime(time.Now().Add(-time.Hour)), + MessageType: chat1.MessageType_TEXT, + }) + decoration, snippet, _ = GetMsgSnippet(ctx, nil, gregor1.UID{}, msg, conv, "alice") + require.Equal(t, chat1.SnippetDecoration_EXPLODED_MESSAGE, decoration) + require.Equal(t, "Message exploded.", snippet) +} + func TestStripUsernameFromConvName(t *testing.T) { // Only the username as a complete segment is removed; "mikem" inside "zoommikem" must not be stripped require.Equal(t, "joshblum,zoommikem,zoomua", diff --git a/go/ephemeral/common_test.go b/go/ephemeral/common_test.go index dd7473975259..eb2eefdb72be 100644 --- a/go/ephemeral/common_test.go +++ b/go/ephemeral/common_test.go @@ -132,7 +132,10 @@ func TestEphemeralPluralization(t *testing.T) { require.Equal(t, humanMsg, pluralized) pluralized = PluralizeErrorMessage(humanMsg, 2) - require.Equal(t, "2 exploding messages are not available, because this device was created after it was sent", pluralized) + require.Equal(t, "2 exploding messages are not available because this device was created after the messages were sent", pluralized) + + pluralized = PluralizeErrorMessage(humanMsgWithPrefix(MemberAfterEKErrMsg), 3) + require.Equal(t, "3 exploding messages are not available because you joined the team after the messages were sent", pluralized) pluralized = PluralizeErrorMessage(DefaultHumanErrMsg, 2) require.Equal(t, "2 exploding messages are not available", pluralized) diff --git a/go/ephemeral/errors.go b/go/ephemeral/errors.go index 29f0b1ebd009..5bab64893c2b 100644 --- a/go/ephemeral/errors.go +++ b/go/ephemeral/errors.go @@ -82,10 +82,10 @@ func newTransientEphemeralKeyError(err EphemeralKeyError) EphemeralKeyError { const ( DefaultHumanErrMsg = "This exploding message is not available" DefaultPluralHumanErrMsg = "%d exploding messages are not available" - DeviceCloneErrMsg = "cloned devices do not support exploding messages" - DeviceCloneWithOneshotErrMsg = "to support exploding messages in `oneshot` mode, you need a separate paper key for each running instance" - DeviceAfterEKErrMsg = "because this device was created after it was sent" - MemberAfterEKErrMsg = "because you joined the team after it was sent" + DeviceCloneErrMsg = "because this device has been cloned" + DeviceCloneWithOneshotErrMsg = "because this device is running in `oneshot` mode; to support exploding messages in `oneshot` mode, you need a separate paper key for each running instance" + DeviceAfterEKErrMsg = "because this device was created after the message was sent" + MemberAfterEKErrMsg = "because you joined the team after the message was sent" DeviceStaleErrMsg = "because this device wasn't online to generate an exploding key" UserStaleErrMsg = "because you weren't online to generate new exploding keys" ) @@ -159,7 +159,7 @@ func humanMsgWithPrefix(humanMsg string) string { if humanMsg == "" { humanMsg = DefaultHumanErrMsg } else if !strings.Contains(humanMsg, DefaultHumanErrMsg) { - humanMsg = fmt.Sprintf("%s, %s", DefaultHumanErrMsg, humanMsg) + humanMsg = fmt.Sprintf("%s %s", DefaultHumanErrMsg, humanMsg) } return humanMsg }