Changeset 428 in code for trunk/downstream.go


Ignore:
Timestamp:
Nov 24, 2020, 1:13:24 PM (5 years ago)
Author:
contact
Message:

Implement delivery receipts via PING messages

This patch implements basic message delivery receipts via PING and PONG.

When a PRIVMSG or NOTICE message is sent, a PING message with a token is
also sent. The history cursor isn't immediately advanced, instead the
bouncer will wait for a PONG message before doing so.

Self-messages trigger a PING for simplicity's sake. We can't immediately
advance the history cursor in this case, because a prior message might
still have an outstanding PING.

Future work may include optimizations such as removing the need to send
a PING after a self-message, or groupping multiple PING messages
together.

Closes: https://todo.sr.ht/~emersion/soju/11

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/downstream.go

    r427 r428  
    288288
    289289        dc.conn.SendMessage(msg)
     290}
     291
     292// sendMessageWithID sends an outgoing message with the specified internal ID.
     293func (dc *downstreamConn) sendMessageWithID(msg *irc.Message, id string) {
     294        dc.SendMessage(msg)
     295
     296        if id == "" || !dc.messageSupportsHistory(msg) {
     297                return
     298        }
     299
     300        dc.sendPing(id)
     301}
     302
     303// advanceMessageWithID advances history to the specified message ID without
     304// sending a message. This is useful e.g. for self-messages when echo-message
     305// isn't enabled.
     306func (dc *downstreamConn) advanceMessageWithID(msg *irc.Message, id string) {
     307        if id == "" || !dc.messageSupportsHistory(msg) {
     308                return
     309        }
     310
     311        dc.sendPing(id)
     312}
     313
     314// ackMsgID acknowledges that a message has been received.
     315func (dc *downstreamConn) ackMsgID(id string) {
     316        netName, entity, _, _, err := parseMsgID(id)
     317        if err != nil {
     318                dc.logger.Printf("failed to ACK message ID %q: %v", id, err)
     319                return
     320        }
     321
     322        network := dc.user.getNetwork(netName)
     323        if network == nil {
     324                return
     325        }
     326
     327        history, ok := network.history[entity]
     328        if !ok {
     329                return
     330        }
     331
     332        history.clients[dc.clientName] = id
     333}
     334
     335func (dc *downstreamConn) sendPing(msgID string) {
     336        token := "soju-msgid-" + base64.RawURLEncoding.EncodeToString([]byte(msgID))
     337        dc.SendMessage(&irc.Message{
     338                Command: "PING",
     339                Params:  []string{token},
     340        })
     341}
     342
     343func (dc *downstreamConn) handlePong(token string) {
     344        if !strings.HasPrefix(token, "soju-msgid-") {
     345                dc.logger.Printf("received unrecognized PONG token %q", token)
     346                return
     347        }
     348        token = strings.TrimPrefix(token, "soju-msgid-")
     349        b, err := base64.RawURLEncoding.DecodeString(token)
     350        if err != nil {
     351                dc.logger.Printf("received malformed PONG token: %v", err)
     352                return
     353        }
     354        msgID := string(b)
     355
     356        dc.ackMsgID(msgID)
    290357}
    291358
     
    876943}
    877944
     945// messageSupportsHistory checks whether the provided message can be sent as
     946// part of an history batch.
     947func (dc *downstreamConn) messageSupportsHistory(msg *irc.Message) bool {
     948        // Don't replay all messages, because that would mess up client
     949        // state. For instance we just sent the list of users, sending
     950        // PART messages for one of these users would be incorrect.
     951        // TODO: add support for draft/event-playback
     952        switch msg.Command {
     953        case "PRIVMSG", "NOTICE":
     954                return true
     955        }
     956        return false
     957}
     958
    878959func (dc *downstreamConn) sendNetworkHistory(net *network) {
    879960        if dc.caps["draft/chathistory"] || dc.user.msgStore == nil {
     
    907988
    908989                for _, msg := range history {
    909                         // Don't replay all messages, because that would mess up client
    910                         // state. For instance we just sent the list of users, sending
    911                         // PART messages for one of these users would be incorrect.
    912                         ignore := true
    913                         switch msg.Command {
    914                         case "PRIVMSG", "NOTICE":
    915                                 ignore = false
    916                         }
    917                         if ignore {
     990                        if !dc.messageSupportsHistory(msg) {
    918991                                continue
    919992                        }
     
    9841057                })
    9851058                return nil
     1059        case "PONG":
     1060                if len(msg.Params) == 0 {
     1061                        return newNeedMoreParamsError(msg.Command)
     1062                }
     1063                token := msg.Params[len(msg.Params)-1]
     1064                dc.handlePong(token)
    9861065        case "USER":
    9871066                return ircError{&irc.Message{
Note: See TracChangeset for help on using the changeset viewer.