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
107
108
109
110
111
112
package pdsclient
import (
"context"
"crypto/tls"
"fmt"
"net"
"net/http"
"strings"
"time"
"github.com/johnandersen777/socialweb-computer-kcp/internal/atproto/xrpc"
"github.com/johnandersen777/socialweb-computer-kcp/internal/kit/nettransport"
"github.com/bluesky-social/indigo/atproto/syntax"
)
const (
remoteDialTimeout = 30 * time.Second
remoteKeepAlive = 30 * time.Second
remoteTLSHandshake = 10 * time.Second
remoteIdleConnTimeout = 90 * time.Second
remoteMaxIdleConns = 32
)
type RemoteConfig struct {
BaseURL string
DID syntax.DID
HTTPClient *http.Client
TLS *tls.Config
Tokens TokenSource
DPoP DPoPSource
Auth xrpc.Auth
Timeout time.Duration
}
type RemotePDS struct {
*basePDS
}
var _ PDS = (*RemotePDS)(nil)
var _ Ref = CRRef{}
func NewRemote(cfg RemoteConfig) (*RemotePDS, error) {
baseURL, err := parseBaseURL(cfg.BaseURL, "http", "https")
if err != nil {
return nil, err
}
expectedDID := cfg.DID
if strings.TrimSpace(expectedDID.String()) != "" {
parsed, err := syntax.ParseDID(expectedDID.String())
if err != nil {
return nil, fmt.Errorf("%w: invalid PDS did", ErrInvalidConfig)
}
expectedDID = parsed
} else {
expectedDID = ""
}
tlsConfig := cfg.TLS
if tlsConfig != nil {
tlsConfig = tlsConfig.Clone()
}
httpClient := cfg.HTTPClient
if httpClient == nil {
httpClient = newRemoteHTTPClient(tlsConfig)
} else {
httpClient = cloneNoRedirect(httpClient)
}
client := newXRPCClient(baseURL, httpClient, cfg.Tokens, cfg.DPoP, cfg.Auth, cfg.Timeout)
client.expectedDID = expectedDID
dial := func(ctx context.Context, cursor *int64) (RepoEventStream, error) {
return newRemoteSubscribe(ctx, baseURL, tlsConfig, cursor)
}
return &RemotePDS{basePDS: &basePDS{client: client, dial: dial}}, nil
}
func (p *RemotePDS) RefreshSession(ctx context.Context, refreshToken string) (Session, error) {
if p == nil || p.basePDS == nil || p.client == nil {
return Session{}, fmt.Errorf("%w: client is nil", ErrInvalidConfig)
}
if strings.TrimSpace(refreshToken) == "" {
return Session{}, fmt.Errorf("%w: no refresh token is available", ErrAuthenticationRequired)
}
var out sessionResponse
if err := p.client.postJSON(ctx, "com.atproto.server.refreshSession", nil, bearer(refreshToken), &out); err != nil {
return Session{}, err
}
session, err := sessionFromResponse(out)
if err != nil {
return Session{}, err
}
p.client.session.set(session)
return session, nil
}
func newRemoteHTTPClient(tlsConfig *tls.Config) *http.Client {
transport := nettransport.WithDial(&http.Transport{
Proxy: nil,
ForceAttemptHTTP2: true,
MaxIdleConns: remoteMaxIdleConns,
IdleConnTimeout: remoteIdleConnTimeout,
TLSHandshakeTimeout: remoteTLSHandshake,
ExpectContinueTimeout: time.Second,
TLSClientConfig: tlsConfig,
}, (&net.Dialer{Timeout: remoteDialTimeout, KeepAlive: remoteKeepAlive}).DialContext)
return &http.Client{
Transport: transport,
CheckRedirect: func(*http.Request, []*http.Request) error {
return http.ErrUseLastResponse
},
}
}