1package idresolver
2
3import (
4 "context"
5 "net"
6 "net/http"
7 "sync"
8 "time"
9
10 "github.com/bluesky-social/indigo/atproto/identity"
11 "github.com/bluesky-social/indigo/atproto/identity/redisdir"
12 "github.com/bluesky-social/indigo/atproto/syntax"
13 "github.com/carlmjohnson/versioninfo"
14 "tangled.sh/tangled.sh/core/appview/config"
15)
16
17type Resolver struct {
18 directory identity.Directory
19}
20
21func BaseDirectory() identity.Directory {
22 base := identity.BaseDirectory{
23 PLCURL: identity.DefaultPLCURL,
24 HTTPClient: http.Client{
25 Timeout: time.Second * 10,
26 Transport: &http.Transport{
27 // would want this around 100ms for services doing lots of handle resolution. Impacts PLC connections as well, but not too bad.
28 IdleConnTimeout: time.Millisecond * 1000,
29 MaxIdleConns: 100,
30 },
31 },
32 Resolver: net.Resolver{
33 Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
34 d := net.Dialer{Timeout: time.Second * 3}
35 return d.DialContext(ctx, network, address)
36 },
37 },
38 TryAuthoritativeDNS: true,
39 // primary Bluesky PDS instance only supports HTTP resolution method
40 SkipDNSDomainSuffixes: []string{".bsky.social"},
41 UserAgent: "indigo-identity/" + versioninfo.Short(),
42 }
43 return &base
44}
45
46func RedisDirectory(url string) (identity.Directory, error) {
47 hitTTL := time.Hour * 24
48 errTTL := time.Second * 30
49 invalidHandleTTL := time.Minute * 5
50 return redisdir.NewRedisDirectory(BaseDirectory(), url, hitTTL, errTTL, invalidHandleTTL, 10000)
51}
52
53func DefaultResolver() *Resolver {
54 return &Resolver{
55 directory: identity.DefaultDirectory(),
56 }
57}
58
59func RedisResolver(config config.RedisConfig) (*Resolver, error) {
60 directory, err := RedisDirectory(config.ToURL())
61 if err != nil {
62 return nil, err
63 }
64 return &Resolver{
65 directory: directory,
66 }, nil
67}
68
69func (r *Resolver) ResolveIdent(ctx context.Context, arg string) (*identity.Identity, error) {
70 id, err := syntax.ParseAtIdentifier(arg)
71 if err != nil {
72 return nil, err
73 }
74
75 return r.directory.Lookup(ctx, *id)
76}
77
78func (r *Resolver) ResolveIdents(ctx context.Context, idents []string) []*identity.Identity {
79 results := make([]*identity.Identity, len(idents))
80 var wg sync.WaitGroup
81
82 done := make(chan struct{})
83 defer close(done)
84
85 for idx, ident := range idents {
86 wg.Add(1)
87 go func(index int, id string) {
88 defer wg.Done()
89
90 select {
91 case <-ctx.Done():
92 results[index] = nil
93 case <-done:
94 results[index] = nil
95 default:
96 identity, _ := r.ResolveIdent(ctx, id)
97 results[index] = identity
98 }
99 }(idx, ident)
100 }
101
102 wg.Wait()
103 return results
104}