forked from
tangled.org/core
Monorepo for Tangled
1package idresolver
2
3import (
4 "context"
5 "fmt"
6 "net"
7 "net/http"
8 "sync"
9 "time"
10
11 "github.com/bluesky-social/indigo/atproto/identity"
12 "github.com/bluesky-social/indigo/atproto/identity/redisdir"
13 "github.com/bluesky-social/indigo/atproto/syntax"
14 "github.com/bluesky-social/indigo/xrpc"
15 "github.com/carlmjohnson/versioninfo"
16)
17
18type Resolver struct {
19 directory identity.Directory
20}
21
22func BaseDirectory(plcUrl string) identity.Directory {
23 base := identity.BaseDirectory{
24 PLCURL: plcUrl,
25 HTTPClient: http.Client{
26 Timeout: time.Second * 10,
27 Transport: &http.Transport{
28 // would want this around 100ms for services doing lots of handle resolution. Impacts PLC connections as well, but not too bad.
29 IdleConnTimeout: time.Millisecond * 1000,
30 MaxIdleConns: 100,
31 },
32 },
33 Resolver: net.Resolver{
34 Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
35 d := net.Dialer{Timeout: time.Second * 3}
36 return d.DialContext(ctx, network, address)
37 },
38 },
39 TryAuthoritativeDNS: true,
40 // primary Bluesky PDS instance only supports HTTP resolution method
41 SkipDNSDomainSuffixes: []string{".bsky.social"},
42 UserAgent: "indigo-identity/" + versioninfo.Short(),
43 }
44 return &base
45}
46
47func RedisDirectory(url, plcUrl string) (identity.Directory, error) {
48 hitTTL := time.Hour * 24
49 errTTL := time.Second * 30
50 invalidHandleTTL := time.Minute * 5
51 return redisdir.NewRedisDirectory(
52 BaseDirectory(plcUrl),
53 url,
54 hitTTL,
55 errTTL,
56 invalidHandleTTL,
57 10000,
58 )
59}
60
61func DefaultResolver(plcUrl string) *Resolver {
62 base := BaseDirectory(plcUrl)
63 cached := identity.NewCacheDirectory(base, 250_000, time.Hour*24, time.Minute*2, time.Minute*5)
64 return &Resolver{
65 directory: &cached,
66 }
67}
68
69func RedisResolver(redisUrl, plcUrl string) (*Resolver, error) {
70 directory, err := RedisDirectory(redisUrl, plcUrl)
71 if err != nil {
72 return nil, err
73 }
74 return &Resolver{
75 directory: directory,
76 }, nil
77}
78
79func (r *Resolver) ResolveIdent(ctx context.Context, arg string) (*identity.Identity, error) {
80 id, err := syntax.ParseAtIdentifier(arg)
81 if err != nil {
82 return nil, err
83 }
84
85 return r.directory.Lookup(ctx, *id)
86}
87
88// PDSClient resolves an identity and returns an XRPC client pointing at the
89// identity's PDS endpoint. This consolidates the common pattern of resolving
90// a DID, validating the handle, and constructing an XRPC client for the PDS.
91func (r *Resolver) PDSClient(ctx context.Context, did string) (*identity.Identity, *xrpc.Client, error) {
92 ident, err := r.ResolveIdent(ctx, did)
93 if err != nil {
94 return nil, nil, fmt.Errorf("failed to resolve identity: %w", err)
95 }
96 if ident.Handle.IsInvalidHandle() {
97 return nil, nil, fmt.Errorf("invalid handle for %s", did)
98 }
99 pdsEndpoint := ident.PDSEndpoint()
100 if pdsEndpoint == "" {
101 return nil, nil, fmt.Errorf("no PDS endpoint for %s", did)
102 }
103 return ident, &xrpc.Client{Host: pdsEndpoint}, nil
104}
105
106func (r *Resolver) ResolveIdents(ctx context.Context, idents []string) []*identity.Identity {
107 results := make([]*identity.Identity, len(idents))
108 var wg sync.WaitGroup
109
110 done := make(chan struct{})
111 defer close(done)
112
113 for idx, ident := range idents {
114 wg.Add(1)
115 go func(index int, id string) {
116 defer wg.Done()
117
118 select {
119 case <-ctx.Done():
120 results[index] = nil
121 case <-done:
122 results[index] = nil
123 default:
124 identity, _ := r.ResolveIdent(ctx, id)
125 results[index] = identity
126 }
127 }(idx, ident)
128 }
129
130 wg.Wait()
131 return results
132}
133
134func (r *Resolver) InvalidateIdent(ctx context.Context, arg string) error {
135 id, err := syntax.ParseAtIdentifier(arg)
136 if err != nil {
137 return err
138 }
139
140 return r.directory.Purge(ctx, *id)
141}
142
143func (r *Resolver) Directory() identity.Directory {
144 return r.directory
145}