Monorepo for Tangled
at local-dev 145 lines 3.7 kB view raw
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}