Monorepo for Tangled
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at master 160 lines 4.3 kB view raw
1package idresolver 2 3import ( 4 "context" 5 "fmt" 6 "net" 7 "net/http" 8 "slices" 9 "strings" 10 "sync" 11 "time" 12 13 "github.com/bluesky-social/indigo/atproto/identity" 14 "github.com/bluesky-social/indigo/atproto/identity/redisdir" 15 "github.com/bluesky-social/indigo/atproto/syntax" 16 "github.com/carlmjohnson/versioninfo" 17) 18 19type Resolver struct { 20 directory identity.Directory 21 base *identity.BaseDirectory 22} 23 24func BaseDirectory(plcUrl string) *identity.BaseDirectory { 25 base := identity.BaseDirectory{ 26 PLCURL: plcUrl, 27 HTTPClient: http.Client{ 28 Timeout: time.Second * 10, 29 Transport: &http.Transport{ 30 // would want this around 100ms for services doing lots of handle resolution. Impacts PLC connections as well, but not too bad. 31 IdleConnTimeout: time.Millisecond * 1000, 32 MaxIdleConns: 100, 33 }, 34 }, 35 Resolver: net.Resolver{ 36 Dial: func(ctx context.Context, network, address string) (net.Conn, error) { 37 d := net.Dialer{Timeout: time.Second * 3} 38 return d.DialContext(ctx, network, address) 39 }, 40 }, 41 TryAuthoritativeDNS: true, 42 // primary Bluesky PDS instance only supports HTTP resolution method 43 SkipDNSDomainSuffixes: []string{".bsky.social"}, 44 UserAgent: "indigo-identity/" + versioninfo.Short(), 45 } 46 return &base 47} 48 49func DefaultResolver(plcUrl string) *Resolver { 50 base := BaseDirectory(plcUrl) 51 cached := identity.NewCacheDirectory(base, 250_000, time.Hour*24, time.Minute*2, time.Minute*5) 52 return &Resolver{ 53 directory: cached, 54 base: base, 55 } 56} 57 58func RedisDirectory(base *identity.BaseDirectory, url string) (identity.Directory, error) { 59 hitTTL := time.Hour * 24 60 errTTL := time.Second * 30 61 invalidHandleTTL := time.Minute * 5 62 return redisdir.NewRedisDirectory(base, url, hitTTL, errTTL, invalidHandleTTL, 10000) 63} 64 65func RedisResolver(redisUrl, plcUrl string) (*Resolver, error) { 66 base := BaseDirectory(plcUrl) 67 directory, err := RedisDirectory(base, redisUrl) 68 if err != nil { 69 return nil, err 70 } 71 return &Resolver{ 72 directory: directory, 73 base: base, 74 }, nil 75} 76 77type handleResolver interface { 78 ResolveHandle(ctx context.Context, h syntax.Handle) (syntax.DID, error) 79} 80 81func (r *Resolver) ResolveHandle(ctx context.Context, handle syntax.Handle) (syntax.DID, error) { 82 if hr, ok := r.directory.(handleResolver); ok { 83 return hr.ResolveHandle(ctx, handle) 84 } 85 return r.base.ResolveHandle(ctx, handle) 86} 87 88func (r *Resolver) ResolveAtIdentifier(ctx context.Context, input string) (*identity.Identity, error) { 89 if did, err := syntax.ParseDID(input); err == nil { 90 return r.directory.LookupDID(ctx, did) 91 } 92 handle, err := syntax.ParseHandle(input) 93 if err != nil { 94 return nil, fmt.Errorf("not a did or handle: %w", err) 95 } 96 handle = handle.Normalize() 97 did, err := r.base.ResolveHandle(ctx, handle) 98 if err != nil { 99 return nil, fmt.Errorf("resolve handle %q: %w", handle, err) 100 } 101 ident, err := r.directory.LookupDID(ctx, did) 102 if err != nil { 103 return nil, fmt.Errorf("lookup did for %q: %w", handle, err) 104 } 105 aka := "at://" + handle.String() 106 if !slices.ContainsFunc(ident.AlsoKnownAs, func(s string) bool { return strings.EqualFold(s, aka) }) { 107 return nil, fmt.Errorf("handle %q not declared in alsoKnownAs for %s", handle, did) 108 } 109 return ident, nil 110} 111 112func (r *Resolver) ResolveIdent(ctx context.Context, arg string) (*identity.Identity, error) { 113 id, err := syntax.ParseAtIdentifier(arg) 114 if err != nil { 115 return nil, err 116 } 117 118 return r.directory.Lookup(ctx, id) 119} 120 121func (r *Resolver) ResolveIdents(ctx context.Context, idents []string) []*identity.Identity { 122 results := make([]*identity.Identity, len(idents)) 123 var wg sync.WaitGroup 124 125 done := make(chan struct{}) 126 defer close(done) 127 128 for idx, ident := range idents { 129 wg.Add(1) 130 go func(index int, id string) { 131 defer wg.Done() 132 133 select { 134 case <-ctx.Done(): 135 results[index] = nil 136 case <-done: 137 results[index] = nil 138 default: 139 identity, _ := r.ResolveIdent(ctx, id) 140 results[index] = identity 141 } 142 }(idx, ident) 143 } 144 145 wg.Wait() 146 return results 147} 148 149func (r *Resolver) InvalidateIdent(ctx context.Context, arg string) error { 150 id, err := syntax.ParseAtIdentifier(arg) 151 if err != nil { 152 return err 153 } 154 155 return r.directory.Purge(ctx, id) 156} 157 158func (r *Resolver) Directory() identity.Directory { 159 return r.directory 160}