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)
}