package hold import ( "context" "fmt" "sync" "time" "github.com/bluesky-social/indigo/atproto/identity" "github.com/bluesky-social/indigo/atproto/syntax" ) // handleCache provides caching for DID → handle resolution // This reduces latency for pattern matching authorization checks type handleCache struct { mu sync.RWMutex cache map[string]cacheEntry // did → handle } type cacheEntry struct { handle string expiresAt time.Time } const handleCacheTTL = 10 * time.Minute var ( // Global handle cache instance globalHandleCache = &handleCache{ cache: make(map[string]cacheEntry), } ) // get retrieves a cached handle for a DID func (c *handleCache) get(did string) (string, bool) { c.mu.RLock() defer c.mu.RUnlock() entry, ok := c.cache[did] if !ok || time.Now().After(entry.expiresAt) { return "", false } return entry.handle, true } // set stores a handle in the cache func (c *handleCache) set(did, handle string) { c.mu.Lock() defer c.mu.Unlock() c.cache[did] = cacheEntry{ handle: handle, expiresAt: time.Now().Add(handleCacheTTL), } } // resolveHandle resolves a DID to its current handle using ATProto identity resolution // Results are cached for 10 minutes to reduce latency func resolveHandle(did string) (string, error) { // Check cache first if handle, ok := globalHandleCache.get(did); ok { return handle, nil } // Cache miss - resolve from network ctx := context.Background() directory := identity.DefaultDirectory() didParsed, err := syntax.ParseDID(did) if err != nil { return "", fmt.Errorf("invalid DID: %w", err) } ident, err := directory.LookupDID(ctx, didParsed) if err != nil { return "", fmt.Errorf("failed to resolve DID: %w", err) } handle := ident.Handle.String() if handle == "" || handle == "handle.invalid" { return "", fmt.Errorf("no valid handle found for DID") } // Cache the result globalHandleCache.set(did, handle) return handle, nil }