From c03625073165240684de4d59568d6157c50b22d2 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sat, 11 Mar 2023 16:24:18 +0200 Subject: [PATCH] Implement MSC2659 --- appservice/http.go | 12 ++++++++++++ bridge/bridge.go | 41 ++++++++++++++++++++++++++++++++++------- client.go | 11 +++++++++++ error.go | 5 +++++ responses.go | 4 ++++ 5 files changed, 66 insertions(+), 7 deletions(-) diff --git a/appservice/http.go b/appservice/http.go index 21fc0961..85b2893a 100644 --- a/appservice/http.go +++ b/appservice/http.go @@ -31,6 +31,8 @@ func (as *AppService) Start() { as.Router.HandleFunc("/_matrix/app/v1/transactions/{txnID}", as.PutTransaction).Methods(http.MethodPut) as.Router.HandleFunc("/_matrix/app/v1/rooms/{roomAlias}", as.GetRoom).Methods(http.MethodGet) as.Router.HandleFunc("/_matrix/app/v1/users/{userID}", as.GetUser).Methods(http.MethodGet) + as.Router.HandleFunc("/_matrix/app/v1/ping", as.PostPing).Methods(http.MethodPost) + as.Router.HandleFunc("/_matrix/app/unstable/fi.mau.msc2659/ping", as.PostPing).Methods(http.MethodPost) as.Router.HandleFunc("/_matrix/mau/live", as.GetLive).Methods(http.MethodGet) as.Router.HandleFunc("/_matrix/mau/ready", as.GetReady).Methods(http.MethodGet) @@ -289,6 +291,16 @@ func (as *AppService) GetUser(w http.ResponseWriter, r *http.Request) { } } +func (as *AppService) PostPing(w http.ResponseWriter, r *http.Request) { + if !as.CheckServerToken(w, r) { + return + } + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write([]byte("{}")) +} + func (as *AppService) GetLive(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "application/json") if as.Live { diff --git a/bridge/bridge.go b/bridge/bridge.go index c3e60c1c..63f12e90 100644 --- a/bridge/bridge.go +++ b/bridge/bridge.go @@ -7,6 +7,7 @@ package bridge import ( + "encoding/json" "errors" "fmt" "net/url" @@ -291,15 +292,40 @@ func (br *Bridge) ensureConnection() { } br.ZLog.Err(err).Msg("Failed to connect to homeserver, retrying in 10 seconds...") time.Sleep(10 * time.Second) + continue } else if resp.UserID != br.Bot.UserID { br.ZLog.WithLevel(zerolog.FatalLevel). Stringer("got_user_id", resp.UserID). Stringer("expected_user_id", br.Bot.UserID). Msg("Unexpected user ID in whoami call") os.Exit(17) + } + + if br.SpecVersions.UnstableFeatures["fi.mau.msc2659"] && br.AS.Host.Port != 0 { + resp, err := br.Bot.AppservicePing() + if err != nil { + evt := br.ZLog.WithLevel(zerolog.FatalLevel).Err(err) + var httpErr mautrix.HTTPError + if errors.As(err, &httpErr) && httpErr.RespError != nil { + if val, ok := httpErr.RespError.ExtraData["body"].(string); ok { + val = strings.TrimSpace(val) + valBytes := []byte(val) + if json.Valid(valBytes) { + evt.RawJSON("body", valBytes) + } else { + evt.Str("body", val) + } + } + } + evt.Msg("Homeserver -> bridge connection is not working") + os.Exit(13) + } + br.ZLog.Debug().Int64("duration_ms", resp.DurationMS).Msg("Homeserver -> bridge connection works") } else { - break + br.ZLog.Debug().Msg("Homeserver does not support checking status of homeserver -> bridge connection") } + + break } } @@ -518,6 +544,13 @@ func (br *Bridge) start() { br.LogDBUpgradeErrorAndExit("matrix_state", err) } + if br.AS.Host.Port != 0 { + br.ZLog.Debug().Msg("Starting application service HTTP server") + go br.AS.Start() + } else { + br.ZLog.Debug().Msg("Appservice port not configured, not starting HTTP server") + } + br.ZLog.Debug().Msg("Checking connection to homeserver") br.ensureConnection() go br.fetchMediaConfig() @@ -530,12 +563,6 @@ func (br *Bridge) start() { } } - if br.AS.Host.Port != 0 { - br.ZLog.Debug().Msg("Starting application service HTTP server") - go br.AS.Start() - } else { - br.ZLog.Debug().Msg("Appservice port not configured, not starting HTTP server") - } br.ZLog.Debug().Msg("Starting event processor") br.EventProcessor.Start() diff --git a/client.go b/client.go index 27a299dc..ed94b802 100644 --- a/client.go +++ b/client.go @@ -1963,6 +1963,17 @@ func (cli *Client) BatchSend(roomID id.RoomID, req *ReqBatchSend) (resp *RespBat return } +func (cli *Client) AppservicePing() (resp *RespAppservicePing, err error) { + _, err = cli.MakeFullRequest(FullRequest{ + Method: http.MethodPost, + URL: cli.BuildClientURL("unstable", "fi.mau.msc2659", "appservice", "ping"), + ResponseJSON: &resp, + // This endpoint intentionally returns 50x, so don't retry + MaxAttempts: 1, + }) + return +} + func (cli *Client) BeeperMergeRooms(req *ReqBeeperMergeRoom) (resp *RespBeeperMergeRoom, err error) { urlPath := cli.BuildClientURL("unstable", "com.beeper.chatmerging", "merge") _, err = cli.MakeRequest(http.MethodPost, urlPath, req, &resp) diff --git a/error.go b/error.go index 36fa74f5..9c5e8a0e 100644 --- a/error.go +++ b/error.go @@ -60,6 +60,11 @@ var ( MIncompatibleRoomVersion = RespError{ErrCode: "M_INCOMPATIBLE_ROOM_VERSION"} // The client specified a parameter that has the wrong value. MInvalidParam = RespError{ErrCode: "M_INVALID_PARAM"} + + MSC2659URLNotSet = RespError{ErrCode: "FI.MAU.MSC2659_URL_NOT_SET"} + MSC2659BadStatus = RespError{ErrCode: "FI.MAU.MSC2659_BAD_STATUS"} + MSC2659ConnectionTimeout = RespError{ErrCode: "FI.MAU.MSC2659_CONNECTION_TIMEOUT"} + MSC2659ConnectionFailed = RespError{ErrCode: "FI.MAU.MSC2659_CONNECTION_FAILED"} ) // HTTPError An HTTP Error response, which may wrap an underlying native Go Error. diff --git a/responses.go b/responses.go index e6b7b61e..ac6bbfe7 100644 --- a/responses.go +++ b/responses.go @@ -552,6 +552,10 @@ type StrippedStateWithTime struct { Timestamp jsontime.UnixMilli `json:"origin_server_ts"` } +type RespAppservicePing struct { + DurationMS int64 `json:"duration"` +} + type RespBeeperMergeRoom RespCreateRoom type RespBeeperSplitRoom struct {