1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
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
}