package cilogs import ( "context" "errors" "fmt" "net" "net/http" "net/url" "strings" "github.com/bluesky-social/indigo/atproto/identity" "github.com/bluesky-social/indigo/atproto/syntax" indigoxrpc "github.com/bluesky-social/indigo/xrpc" "github.com/gorilla/websocket" "tangled.org/core/api/org_tangled" "tangled.org/core/hostutil" extlexutil "tangled.org/core/lexutil" "tangled.org/core/spindle/knotclient" ) var ErrNoSpindle = errors.New("no spindle configured for this repo") type Spindles struct { knots *knotclient.Client http *http.Client dialer *net.Dialer } // the manifest can name any host, so outside dev the calls refuse special addresses func NewSpindles(dir identity.Directory, dev bool) *Spindles { httpc := knotclient.HTTPClient(dev) return &Spindles{knots: knotclient.New(dir, httpc, dev), http: httpc, dialer: knotclient.Dialer(dev)} } func (s *Spindles) Of(ctx context.Context, repo syntax.DID) (*extlexutil.Client, error) { knot, err := s.knots.KnotOf(ctx, repo) if err != nil { return nil, err } var manifest org_tangled.RepoManifest if err := s.knots.GetRecord(ctx, knot, repo, org_tangled.RepoManifestNSID, knotclient.ManifestRkey, &manifest); err != nil { return nil, err } if manifest.Spindle == nil || *manifest.Spindle == "" { return nil, ErrNoSpindle } base, err := spindleURL(*manifest.Spindle) if err != nil { return nil, err } return &extlexutil.Client{ Client: indigoxrpc.Client{Host: base, Client: s.http}, Dialer: websocket.Dialer{NetDialContext: s.dialer.DialContext}, }, nil } // the spindle serves xrpc on the did:web host, like hostForKnot in web func spindleURL(did string) (string, error) { host, ok := strings.CutPrefix(did, "did:web:") if !ok || strings.Contains(host, ":") { return "", fmt.Errorf("spindle %q is not a did:web host", did) } host, err := url.PathUnescape(host) if err != nil { return "", fmt.Errorf("spindle %q: %w", did, err) } return hostutil.EnsureHttpScheme(host) }