package main import ( "context" "fmt" "log/slog" "net/http" "net/url" "time" "github.com/bluesky-social/indigo/events" "github.com/gorilla/websocket" ) type Firehose struct { Log *slog.Logger Host string Seq int64 events chan<- *events.XRPCStreamEvent } func (fh *Firehose) subscribeWithRedialer(ctx context.Context, fhevents chan<- *events.XRPCStreamEvent) error { defer close(fhevents) d := websocket.Dialer{} rurl, err := url.Parse(fh.Host) if err != nil { rurl = new(url.URL) rurl.Host = fh.Host rurl.Scheme = "wss" } else { if rurl.Scheme == fh.Host { rurl.Scheme = "wss" } if rurl.Scheme == "https" || rurl.Scheme == "wss" { rurl.Scheme = "wss" } else if rurl.Scheme == "http" || rurl.Scheme == "ws" { rurl.Scheme = "ws" } else if rurl.Scheme == "" { rurl.Scheme = "wss" } else { return fmt.Errorf("host unknown scheme %#v", rurl.Scheme) } } //protocol := "wss" subscribeReposUrl := rurl.JoinPath("/xrpc/com.atproto.sync.subscribeRepos") fh.events = fhevents var backoff int for { select { case <-ctx.Done(): return nil default: } header := http.Header{ "User-Agent": []string{"collectiondir"}, } if fh.Seq >= 0 { subscribeReposUrl.RawQuery = fmt.Sprintf("cursor=%d", fh.Seq) } url := subscribeReposUrl.String() con, res, err := d.DialContext(ctx, url, header) if err != nil { fh.Log.Warn("dialing failed", "url", url, "err", err, "backoff", backoff) time.Sleep(time.Duration(5+backoff) * time.Second) backoff++ continue } else { backoff = 0 } fh.Log.Info("event subscription response", "code", res.StatusCode) if err := fh.handleConnection(ctx, con); err != nil { fh.Log.Warn("connection failed", "host", fh.Host, "err", err) } } } func (fh *Firehose) handleConnection(ctx context.Context, con *websocket.Conn) error { ctx, cancel := context.WithCancel(ctx) defer cancel() return events.HandleRepoStream(ctx, con, fh, fh.Log) } // AddWork is part of events.Scheduler func (fh *Firehose) AddWork(ctx context.Context, repo string, val *events.XRPCStreamEvent) error { tsv, ok := val.GetSequence() if ok { fh.Seq = tsv } fh.events <- val return nil } // Shutdown is part of events.Scheduler func (fh *Firehose) Shutdown() { // unneeded in this usage }