package participant import ( "context" "errors" "fmt" "log/slog" "net/http" "net/url" "strings" "github.com/johnandersen777/socialweb-computer-kcp/internal/atproto/xrpc" ) const ( OutboundReplyLimit = 4096 relayNoSubscriber = "no active subscriber" ) type CallError struct { Status int Err error } func (e *CallError) HTTPStatus() int { return e.Status } func (e *CallError) Error() string { if e == nil { return "" } if e.Err != nil { return e.Err.Error() } return fmt.Sprintf("HTTP %d", e.Status) } func (e *CallError) Unwrap() error { if e == nil { return nil } return e.Err } func Refused(err error) bool { callErr, ok := errors.AsType[*CallError](err) if !ok { return false } name, message := "", "" if xerr, ok := errors.AsType[*xrpc.Error](err); ok { name, message = xerr.Name, xerr.Message } return RefusedReply(callErr.Status, name, message) } func RefusedReply(status int, name, message string) bool { switch { case status < 400 || status > 499 || status == http.StatusRequestTimeout || status == http.StatusTooManyRequests: return false case status == http.StatusNotFound: return strings.TrimSpace(name) != "" && !strings.Contains(message, relayNoSubscriber) } return true } type Outbound struct { HTTP *http.Client Tokens *Tokens Log *slog.Logger } func (o *Outbound) log() *slog.Logger { if o == nil || o.Log == nil { return slog.New(slog.DiscardHandler) } return o.Log } func (o *Outbound) Post(ctx context.Context, endpoint, nsid string, body any) error { host := HostPort(endpoint) client := &xrpc.Client{Host: endpoint, HTTP: o.HTTP, Limit: OutboundReplyLimit} if host == "" { o.log().Warn("the outbound XRPC call is unsigned: the endpoint has no hostname", "url", endpoint, "nsid", nsid) } else if _, err := o.Tokens.Token(ctx, host, nsid); err != nil { o.log().Warn("the outbound XRPC call is unsigned: the PDS cannot mint a service-auth token", "url", endpoint, "nsid", nsid, "err", err) } else { client.Auth = &xrpc.ServiceAuth{Tokens: o.Tokens.Source(host, nsid)} } _, err := xrpc.Procedure[any, struct{}](ctx, client, nsid, body) if xerr, ok := errors.AsType[*xrpc.Error](err); ok { return &CallError{Status: xerr.Status, Err: xerr} } return err } func HostPort(raw string) string { u, err := url.Parse(raw) if err != nil { return "" } return u.Host }