Only send MSS event after phone confirms sending

This commit is contained in:
Tulir Asokan 2023-07-10 00:34:44 +03:00
parent 0d334623e8
commit 63effcbff6
2 changed files with 60 additions and 60 deletions

View file

@ -20,6 +20,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"strings"
"sync" "sync"
"time" "time"
@ -29,74 +30,51 @@ import (
"maunium.net/go/mautrix/bridge/status" "maunium.net/go/mautrix/bridge/status"
"maunium.net/go/mautrix/event" "maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id" "maunium.net/go/mautrix/id"
"go.mau.fi/mautrix-gmessages/libgm/binary"
) )
var ( var (
errUserNotConnected = errors.New("you are not connected to WhatsApp")
errDifferentUser = errors.New("user is not the recipient of this private chat portal")
errUserNotLoggedIn = errors.New("user is not logged in and chat has no relay bot")
errMNoticeDisabled = errors.New("bridging m.notice messages is disabled") errMNoticeDisabled = errors.New("bridging m.notice messages is disabled")
errUnexpectedParsedContentType = errors.New("unexpected parsed content type") errUnexpectedParsedContentType = errors.New("unexpected parsed content type")
errInvalidGeoURI = errors.New("invalid `geo:` URI in message")
errUnknownMsgType = errors.New("unknown msgtype") errUnknownMsgType = errors.New("unknown msgtype")
errMediaUnsupportedType = errors.New("unsupported media type") errMediaUnsupportedType = errors.New("unsupported media type")
errTargetNotFound = errors.New("target event not found")
errReactionDatabaseNotFound = errors.New("reaction database entry not found")
errReactionTargetNotFound = errors.New("reaction target message not found")
errTargetIsFake = errors.New("target is a fake event")
errReactionSentBySomeoneElse = errors.New("target reaction was sent by someone else")
errDMSentByOtherUser = errors.New("target message was sent by the other user in a DM")
errPollMissingQuestion = errors.New("poll message is missing question")
errPollDuplicateOption = errors.New("poll options must be unique")
errEditUnknownTarget = errors.New("unknown edit target message") errMessageTakingLong = errors.New("bridging the message is taking longer than usual")
errEditUnknownTargetType = errors.New("unsupported edited message type")
errEditDifferentSender = errors.New("can't edit message sent by another user")
errEditTooOld = errors.New("message is too old to be edited")
errBroadcastReactionNotSupported = errors.New("reacting to status messages is not currently supported")
errBroadcastSendDisabled = errors.New("sending status messages is disabled")
errMessageTakingLong = errors.New("bridging the message is taking longer than usual")
errTimeoutBeforeHandling = errors.New("message timed out before handling was started")
) )
type OutgoingStatusError binary.MessageStatusType
func (ose OutgoingStatusError) Error() string {
return strings.TrimPrefix(string((binary.MessageStatusType)(ose).Descriptor().Name()), "OUTGOING_")
}
func (ose OutgoingStatusError) HumanError() string {
return ""
}
func (ose OutgoingStatusError) Is(other error) bool {
otherOSE, ok := other.(OutgoingStatusError)
if !ok {
return false
}
return int(ose) == int(otherOSE)
}
func errorToStatusReason(err error) (reason event.MessageStatusReason, status event.MessageStatus, isCertain, sendNotice bool, humanMessage string) { func errorToStatusReason(err error) (reason event.MessageStatusReason, status event.MessageStatus, isCertain, sendNotice bool, humanMessage string) {
var ose OutgoingStatusError
switch { switch {
case errors.Is(err, errUnexpectedParsedContentType), case errors.Is(err, errUnexpectedParsedContentType),
errors.Is(err, errUnknownMsgType), errors.Is(err, errUnknownMsgType):
errors.Is(err, errInvalidGeoURI),
errors.Is(err, errBroadcastReactionNotSupported),
errors.Is(err, errBroadcastSendDisabled):
return event.MessageStatusUnsupported, event.MessageStatusFail, true, true, "" return event.MessageStatusUnsupported, event.MessageStatusFail, true, true, ""
case errors.Is(err, errMNoticeDisabled): case errors.Is(err, errMNoticeDisabled):
return event.MessageStatusUnsupported, event.MessageStatusFail, true, false, "" return event.MessageStatusUnsupported, event.MessageStatusFail, true, false, ""
case errors.Is(err, errMediaUnsupportedType), case errors.Is(err, errMediaUnsupportedType):
errors.Is(err, errPollMissingQuestion),
errors.Is(err, errPollDuplicateOption),
errors.Is(err, errEditDifferentSender),
errors.Is(err, errEditTooOld),
errors.Is(err, errEditUnknownTarget),
errors.Is(err, errEditUnknownTargetType):
return event.MessageStatusUnsupported, event.MessageStatusFail, true, true, err.Error() return event.MessageStatusUnsupported, event.MessageStatusFail, true, true, err.Error()
case errors.Is(err, errTimeoutBeforeHandling):
return event.MessageStatusTooOld, event.MessageStatusRetriable, true, true, "the message was too old when it reached the bridge, so it was not handled"
case errors.Is(err, context.DeadlineExceeded): case errors.Is(err, context.DeadlineExceeded):
return event.MessageStatusTooOld, event.MessageStatusRetriable, false, true, "handling the message took too long and was cancelled" return event.MessageStatusTooOld, event.MessageStatusRetriable, false, true, "handling the message took too long and was cancelled"
case errors.Is(err, errMessageTakingLong): case errors.As(err, &ose):
return event.MessageStatusTooOld, event.MessageStatusPending, false, true, err.Error() return event.MessageStatusNetworkError, event.MessageStatusFail, true, true, ose.HumanError()
case errors.Is(err, errTargetNotFound),
errors.Is(err, errTargetIsFake),
errors.Is(err, errReactionDatabaseNotFound),
errors.Is(err, errReactionTargetNotFound),
errors.Is(err, errReactionSentBySomeoneElse),
errors.Is(err, errDMSentByOtherUser):
return event.MessageStatusGenericError, event.MessageStatusFail, true, false, ""
case errors.Is(err, errUserNotConnected):
return event.MessageStatusGenericError, event.MessageStatusRetriable, true, true, ""
case errors.Is(err, errUserNotLoggedIn),
errors.Is(err, errDifferentUser):
return event.MessageStatusGenericError, event.MessageStatusRetriable, true, false, ""
default: default:
return event.MessageStatusGenericError, event.MessageStatusRetriable, false, true, "" return event.MessageStatusGenericError, event.MessageStatusRetriable, false, true, ""
} }
@ -209,7 +187,6 @@ func (portal *Portal) sendMessageMetrics(evt *event.Event, err error, part strin
portal.log.Debugfln("Handled Matrix %s %s", msgType, evtDescription) portal.log.Debugfln("Handled Matrix %s %s", msgType, evtDescription)
portal.sendDeliveryReceipt(evt.ID) portal.sendDeliveryReceipt(evt.ID)
portal.bridge.SendMessageSuccessCheckpoint(evt, status.MsgStepRemote, ms.getRetryNum()) portal.bridge.SendMessageSuccessCheckpoint(evt, status.MsgStepRemote, ms.getRetryNum())
portal.sendStatusEvent(origEvtID, evt.ID, nil)
if prevNotice := ms.popNoticeID(); prevNotice != "" { if prevNotice := ms.popNoticeID(); prevNotice != "" {
_, _ = portal.MainIntent().RedactEvent(portal.MXID, prevNotice, mautrix.ReqRedact{ _, _ = portal.MainIntent().RedactEvent(portal.MXID, prevNotice, mautrix.ReqRedact{
Reason: "error resolved", Reason: "error resolved",

View file

@ -30,7 +30,6 @@ import (
"github.com/gabriel-vasile/mimetype" "github.com/gabriel-vasile/mimetype"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"maunium.net/go/maulogger/v2" "maunium.net/go/maulogger/v2"
"maunium.net/go/mautrix" "maunium.net/go/mautrix"
"maunium.net/go/mautrix/appservice" "maunium.net/go/mautrix/appservice"
"maunium.net/go/mautrix/bridge" "maunium.net/go/mautrix/bridge"
@ -190,7 +189,7 @@ func (br *GMBridge) newBlankPortal(key database.Key) *Portal {
messages: make(chan PortalMessage, br.Config.Bridge.PortalMessageBuffer), messages: make(chan PortalMessage, br.Config.Bridge.PortalMessageBuffer),
matrixMessages: make(chan PortalMatrixMessage, br.Config.Bridge.PortalMessageBuffer), matrixMessages: make(chan PortalMatrixMessage, br.Config.Bridge.PortalMessageBuffer),
outgoingMessages: make(map[string]id.EventID), outgoingMessages: make(map[string]*outgoingMessage),
} }
go portal.handleMessageLoop() go portal.handleMessageLoop()
return portal return portal
@ -215,6 +214,12 @@ type PortalMatrixMessage struct {
receivedAt time.Time receivedAt time.Time
} }
type outgoingMessage struct {
*event.Event
Saved bool
Checkpointed bool
}
type Portal struct { type Portal struct {
*database.Portal *database.Portal
@ -234,7 +239,7 @@ type Portal struct {
recentlyHandledLock sync.Mutex recentlyHandledLock sync.Mutex
recentlyHandledIndex uint8 recentlyHandledIndex uint8
outgoingMessages map[string]id.EventID outgoingMessages map[string]*outgoingMessage
outgoingMessagesLock sync.Mutex outgoingMessagesLock sync.Mutex
currentlyTyping []id.UserID currentlyTyping []id.UserID
@ -301,18 +306,36 @@ func (portal *Portal) handleMessageLoop() {
} }
} }
func (portal *Portal) isOutgoingMessage(evt *binary.Message) id.EventID { func (portal *Portal) isOutgoingMessage(msg *binary.Message) id.EventID {
portal.outgoingMessagesLock.Lock() portal.outgoingMessagesLock.Lock()
defer portal.outgoingMessagesLock.Unlock() defer portal.outgoingMessagesLock.Unlock()
evtID, ok := portal.outgoingMessages[evt.TmpID] out, ok := portal.outgoingMessages[msg.TmpID]
if ok { if ok {
delete(portal.outgoingMessages, evt.TmpID) if !out.Saved {
portal.markHandled(evt, map[string]id.EventID{"": evtID}, true) portal.markHandled(msg, map[string]id.EventID{"": out.ID}, true)
return evtID out.Saved = true
}
switch msg.GetMessageStatus().GetStatus() {
case binary.MessageStatusType_OUTGOING_DELIVERED, binary.MessageStatusType_OUTGOING_COMPLETE, binary.MessageStatusType_OUTGOING_DISPLAYED:
delete(portal.outgoingMessages, msg.TmpID)
portal.sendStatusEvent(out.ID, "", nil)
case binary.MessageStatusType_OUTGOING_FAILED_GENERIC,
binary.MessageStatusType_OUTGOING_FAILED_EMERGENCY_NUMBER,
binary.MessageStatusType_OUTGOING_CANCELED,
binary.MessageStatusType_OUTGOING_FAILED_TOO_LARGE,
binary.MessageStatusType_OUTGOING_FAILED_RECIPIENT_LOST_RCS,
binary.MessageStatusType_OUTGOING_FAILED_NO_RETRY_NO_FALLBACK,
binary.MessageStatusType_OUTGOING_FAILED_RECIPIENT_DID_NOT_DECRYPT,
binary.MessageStatusType_OUTGOING_FAILED_RECIPIENT_LOST_ENCRYPTION,
binary.MessageStatusType_OUTGOING_FAILED_RECIPIENT_DID_NOT_DECRYPT_NO_MORE_RETRY:
err := OutgoingStatusError(msg.GetMessageStatus().GetStatus())
portal.sendStatusEvent(out.ID, "", err)
// TODO error notice
}
return out.ID
} }
return "" return ""
} }
func hasInProgressMedia(msg *binary.Message) bool { func hasInProgressMedia(msg *binary.Message) bool {
for _, part := range msg.MessageInfo { for _, part := range msg.MessageInfo {
media, ok := part.GetData().(*binary.MessageInfo_MediaContent) media, ok := part.GetData().(*binary.MessageInfo_MediaContent)
@ -1022,7 +1045,7 @@ func (portal *Portal) HandleMatrixMessage(sender *User, evt *event.Event, timing
txnID := util.GenerateTmpId() txnID := util.GenerateTmpId()
portal.outgoingMessagesLock.Lock() portal.outgoingMessagesLock.Lock()
portal.outgoingMessages[txnID] = evt.ID portal.outgoingMessages[txnID] = &outgoingMessage{Event: evt}
portal.outgoingMessagesLock.Unlock() portal.outgoingMessagesLock.Unlock()
switch content.MsgType { switch content.MsgType {
case event.MsgText, event.MsgEmote, event.MsgNotice: case event.MsgText, event.MsgEmote, event.MsgNotice: