collection of golang services under the Red Dwarf umbrella server.reddwarf.app
bluesky reddwarf microcosm appview
16
fork

Configure Feed

Select the types of activity you want to include in your feed.

backstream init

+1354 -24
+297
backstream/atproto.go
··· 1 + package backstream 2 + 3 + import ( 4 + "context" 5 + "encoding/json" 6 + "fmt" 7 + "io" 8 + "net/http" 9 + "net/url" 10 + "strings" 11 + "sync" 12 + "time" 13 + ) 14 + 15 + type ATProtoClient struct { 16 + HTTPClient *http.Client 17 + RelayHost string 18 + PLCHost string 19 + didCache map[string]string 20 + didCacheLock sync.RWMutex 21 + } 22 + 23 + type ListReposResponse struct { 24 + Cursor string `json:"cursor"` 25 + Repos []RepoInfo `json:"repos"` 26 + } 27 + type RepoInfo struct { 28 + DID string `json:"did"` 29 + Head string `json:"head"` 30 + } 31 + 32 + type ListRecordsResponse struct { 33 + Cursor string `json:"cursor"` 34 + Records []Record `json:"records"` 35 + } 36 + type Record struct { 37 + URI string `json:"uri"` 38 + CID string `json:"cid"` 39 + Value interface{} `json:"value"` 40 + } 41 + 42 + type GetRecordOutput struct { 43 + URI string `json:"uri"` 44 + CID string `json:"cid"` 45 + Value interface{} `json:"value"` 46 + } 47 + 48 + type JetstreamLikeOutput struct { 49 + Did string `json:"did"` 50 + Kind string `json:"kind"` 51 + TimeUS json.Number `json:"time_us"` 52 + Commit JetstreamLikeCommit `json:"commit"` 53 + } 54 + 55 + type JetstreamLikeCommit struct { 56 + Rev string `json:"rev"` 57 + Operation string `json:"operation"` 58 + Collection string `json:"collection"` 59 + RKey string `json:"rkey"` 60 + Record interface{} `json:"record"` 61 + CID string `json:"cid"` 62 + } 63 + 64 + func NewATProtoClient(relayHost, plcHost string) *ATProtoClient { 65 + return &ATProtoClient{ 66 + HTTPClient: &http.Client{Timeout: 30 * time.Second}, 67 + RelayHost: relayHost, 68 + PLCHost: plcHost, 69 + didCache: make(map[string]string), 70 + } 71 + } 72 + 73 + func (c *ATProtoClient) ListRepos(ctx context.Context, cursor string) ([]RepoInfo, string, error) { 74 + u, _ := url.Parse(fmt.Sprintf("%s/xrpc/com.atproto.sync.listRepos", c.RelayHost)) 75 + q := u.Query() 76 + q.Set("limit", "1000") 77 + if cursor != "" { 78 + q.Set("cursor", cursor) 79 + } 80 + u.RawQuery = q.Encode() 81 + 82 + req, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil) 83 + if err != nil { 84 + return nil, "", err 85 + } 86 + 87 + resp, err := c.HTTPClient.Do(req) 88 + if err != nil { 89 + return nil, "", err 90 + } 91 + defer resp.Body.Close() 92 + 93 + if resp.StatusCode != http.StatusOK { 94 + return nil, "", fmt.Errorf("listRepos non-200 status: %s", resp.Status) 95 + } 96 + 97 + var data ListReposResponse 98 + if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { 99 + return nil, "", err 100 + } 101 + 102 + return data.Repos, data.Cursor, nil 103 + } 104 + 105 + func (c *ATProtoClient) ListReposByCollection(ctx context.Context, collection, cursor string) ([]RepoInfo, string, error) { 106 + u, _ := url.Parse(fmt.Sprintf("%s/xrpc/com.atproto.sync.listReposByCollection", c.RelayHost)) 107 + q := u.Query() 108 + q.Set("collection", collection) 109 + q.Set("limit", "500") 110 + if cursor != "" { 111 + q.Set("cursor", cursor) 112 + } 113 + u.RawQuery = q.Encode() 114 + 115 + req, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil) 116 + if err != nil { 117 + return nil, "", err 118 + } 119 + 120 + resp, err := c.HTTPClient.Do(req) 121 + if err != nil { 122 + return nil, "", err 123 + } 124 + defer resp.Body.Close() 125 + 126 + if resp.StatusCode != http.StatusOK { 127 + return nil, "", fmt.Errorf("listReposByCollection non-200 status: %s", resp.Status) 128 + } 129 + 130 + var data ListReposResponse 131 + if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { 132 + return nil, "", err 133 + } 134 + 135 + return data.Repos, data.Cursor, nil 136 + } 137 + 138 + func (c *ATProtoClient) ListRecords(ctx context.Context, pdsURL, repo, collection, cursor string) ([]Record, string, error) { 139 + u, _ := url.Parse(fmt.Sprintf("%s/xrpc/com.atproto.repo.listRecords", pdsURL)) 140 + q := u.Query() 141 + q.Set("repo", repo) 142 + q.Set("collection", collection) 143 + q.Set("limit", "100") 144 + if cursor != "" { 145 + q.Set("cursor", cursor) 146 + } 147 + u.RawQuery = q.Encode() 148 + 149 + req, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil) 150 + if err != nil { 151 + return nil, "", err 152 + } 153 + 154 + resp, err := c.HTTPClient.Do(req) 155 + if err != nil { 156 + return nil, "", err 157 + } 158 + defer resp.Body.Close() 159 + 160 + if resp.StatusCode != http.StatusOK { 161 + body, _ := io.ReadAll(resp.Body) 162 + return nil, "", fmt.Errorf("listRecords non-200 status for %s: %s body: %s", repo, resp.Status, string(body)) 163 + } 164 + 165 + var data ListRecordsResponse 166 + if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { 167 + return nil, "", err 168 + } 169 + 170 + return data.Records, data.Cursor, nil 171 + } 172 + 173 + type didDoc struct { 174 + Service []struct { 175 + ID string `json:"id"` 176 + Type string `json:"type"` 177 + ServiceEndpoint string `json:"serviceEndpoint"` 178 + } `json:"service"` 179 + } 180 + 181 + func (c *ATProtoClient) ResolveDID(ctx context.Context, did string) (string, error) { 182 + c.didCacheLock.RLock() 183 + cachedURL, found := c.didCache[did] 184 + c.didCacheLock.RUnlock() 185 + if found { 186 + return cachedURL, nil 187 + } 188 + 189 + var doc didDoc 190 + var err error 191 + 192 + if strings.HasPrefix(did, "did:plc:") { 193 + doc, err = c.resolvePLC(ctx, did) 194 + } else if strings.HasPrefix(did, "did:web:") { 195 + doc, err = c.resolveWeb(ctx, did) 196 + } else { 197 + return "", fmt.Errorf("unsupported DID method for: %s", did) 198 + } 199 + 200 + if err != nil { 201 + return "", err 202 + } 203 + 204 + for _, s := range doc.Service { 205 + if s.ID == "#atproto_pds" { 206 + c.didCacheLock.Lock() 207 + c.didCache[did] = s.ServiceEndpoint 208 + c.didCacheLock.Unlock() 209 + return s.ServiceEndpoint, nil 210 + } 211 + } 212 + 213 + return "", fmt.Errorf("PDS service endpoint not found in DID document for %s", did) 214 + } 215 + 216 + func (c *ATProtoClient) resolvePLC(ctx context.Context, did string) (didDoc, error) { 217 + u := fmt.Sprintf("%s/%s", c.PLCHost, did) 218 + return c.fetchDIDDoc(ctx, u) 219 + } 220 + 221 + func (c *ATProtoClient) resolveWeb(ctx context.Context, did string) (didDoc, error) { 222 + domain := strings.TrimPrefix(did, "did:web:") 223 + u := fmt.Sprintf("https://%s/.well-known/did.json", domain) 224 + return c.fetchDIDDoc(ctx, u) 225 + } 226 + 227 + func (c *ATProtoClient) fetchDIDDoc(ctx context.Context, url string) (didDoc, error) { 228 + var doc didDoc 229 + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) 230 + if err != nil { 231 + return doc, err 232 + } 233 + 234 + resp, err := c.HTTPClient.Do(req) 235 + if err != nil { 236 + return doc, fmt.Errorf("failed to fetch DID doc from %s: %w", url, err) 237 + } 238 + defer resp.Body.Close() 239 + 240 + if resp.StatusCode != http.StatusOK { 241 + return doc, fmt.Errorf("bad status from DID doc fetch (%s): %s", url, resp.Status) 242 + } 243 + 244 + if err := json.NewDecoder(resp.Body).Decode(&doc); err != nil { 245 + return doc, fmt.Errorf("failed to parse DID doc from %s: %w", url, err) 246 + } 247 + return doc, nil 248 + } 249 + 250 + type DescribeRepoResponse struct { 251 + Collections []string `json:"collections"` 252 + } 253 + 254 + func (c *ATProtoClient) DescribeRepo(ctx context.Context, pdsURL, did string) ([]string, error) { 255 + u := fmt.Sprintf("%s/xrpc/com.atproto.repo.describeRepo?repo=%s", pdsURL, did) 256 + req, err := http.NewRequestWithContext(ctx, "GET", u, nil) 257 + if err != nil { 258 + return nil, err 259 + } 260 + resp, err := c.HTTPClient.Do(req) 261 + if err != nil { 262 + return nil, err 263 + } 264 + defer resp.Body.Close() 265 + if resp.StatusCode != http.StatusOK { 266 + body, _ := io.ReadAll(resp.Body) 267 + return nil, fmt.Errorf("describeRepo non-200 status for %s: %s body: %s", did, resp.Status, string(body)) 268 + } 269 + var data DescribeRepoResponse 270 + if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { 271 + return nil, err 272 + } 273 + return data.Collections, nil 274 + } 275 + 276 + func (c *ATProtoClient) GetRepo(ctx context.Context, pdsURL, did string) (io.ReadCloser, error) { 277 + client := &http.Client{Timeout: 600 * time.Second} 278 + 279 + u := fmt.Sprintf("%s/xrpc/com.atproto.sync.getRepo?did=%s", pdsURL, did) 280 + req, err := http.NewRequestWithContext(ctx, "GET", u, nil) 281 + if err != nil { 282 + return nil, err 283 + } 284 + 285 + resp, err := client.Do(req) 286 + if err != nil { 287 + return nil, fmt.Errorf("getRepo request failed for %s: %w", did, err) 288 + } 289 + 290 + if resp.StatusCode != http.StatusOK { 291 + body, _ := io.ReadAll(resp.Body) 292 + resp.Body.Close() 293 + return nil, fmt.Errorf("getRepo non-200 status for %s: %s body: %s", did, resp.Status, string(body)) 294 + } 295 + 296 + return resp.Body, nil 297 + }
+547
backstream/handler.go
··· 1 + package backstream 2 + 3 + import ( 4 + //"bytes" 5 + "context" 6 + "encoding/json" 7 + "errors" 8 + "fmt" 9 + "log" 10 + "net/http" 11 + "strings" 12 + "sync" 13 + "time" 14 + 15 + "io" 16 + "io/ioutil" 17 + "os" 18 + 19 + "runtime" 20 + "runtime/debug" 21 + 22 + "github.com/gorilla/websocket" 23 + "github.com/klauspost/compress/zstd" 24 + 25 + data "github.com/bluesky-social/indigo/atproto/atdata" 26 + atrepo "github.com/bluesky-social/indigo/atproto/repo" 27 + 28 + // "github.com/bluesky-social/indigo/repo" 29 + "github.com/bluesky-social/indigo/atproto/syntax" 30 + "github.com/ipfs/go-cid" 31 + ) 32 + 33 + const ( 34 + numWorkers = 20 35 + ) 36 + 37 + var DefaultUpgrader = websocket.Upgrader{ 38 + CheckOrigin: func(r *http.Request) bool { 39 + return true 40 + }, 41 + } 42 + 43 + type BackfillHandler struct { 44 + Upgrader websocket.Upgrader 45 + SessionManager *SessionManager 46 + AtpClient *ATProtoClient 47 + ZstdDict []byte 48 + UseGetRepoMethod bool 49 + } 50 + 51 + type BackfillParams struct { 52 + WantedDIDs []string 53 + WantedCollections []string 54 + GetRecordFormat bool 55 + } 56 + 57 + func (h *BackfillHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 58 + compress := (r.URL.Query().Get("compress") == "true") && (h.ZstdDict != nil) 59 + 60 + conn, err := h.Upgrader.Upgrade(w, r, nil) 61 + if err != nil { 62 + log.Printf("Failed to upgrade connection: %v", err) 63 + return 64 + } 65 + defer conn.Close() 66 + 67 + if compress { 68 + log.Println("Client requested zstd compression. Enabling.") 69 + } 70 + 71 + params, ticket, err := h.parseQueryParams(r) 72 + if err != nil { 73 + h.sendError(conn, err.Error()) 74 + return 75 + } 76 + 77 + log.Printf("New connection for ticket: %s. DIDs: %v, Collections: %v, Workers: %d", ticket, params.WantedDIDs, params.WantedCollections, numWorkers) 78 + 79 + session := h.SessionManager.GetOrCreate(ticket, params) 80 + session.LastAccessed = time.Now() 81 + 82 + ctx, cancel := context.WithCancel(r.Context()) 83 + defer cancel() 84 + 85 + go func() { 86 + defer cancel() 87 + for { 88 + if _, _, err := conn.ReadMessage(); err != nil { 89 + if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) { 90 + log.Printf("Client disconnected for ticket %s (read error): %v", ticket, err) 91 + } 92 + break 93 + } 94 + } 95 + }() 96 + 97 + var wg sync.WaitGroup 98 + jobs := make(chan string, numWorkers) 99 + results := make(chan interface{}, 100) 100 + 101 + for i := 1; i <= numWorkers; i++ { 102 + go h.worker(ctx, i, &wg, jobs, results, session) 103 + } 104 + 105 + writerDone := make(chan struct{}) 106 + if compress { 107 + go h.compressedWriter(ctx, cancel, conn, results, writerDone) 108 + } else { 109 + go h.writer(ctx, cancel, conn, results, writerDone) 110 + } 111 + 112 + wg.Add(1) 113 + go h.producer(ctx, &wg, jobs, session) 114 + 115 + wg.Wait() 116 + close(results) 117 + <-writerDone 118 + 119 + log.Printf("Backfill completed for ticket: %s", session.Ticket) 120 + h.sendMessage(conn, map[string]string{"status": "complete", "message": "Backfill finished."}) 121 + } 122 + 123 + func (h *BackfillHandler) compressedWriter(ctx context.Context, cancel context.CancelFunc, conn *websocket.Conn, results <-chan interface{}, done chan<- struct{}) { 124 + defer close(done) 125 + 126 + encoder, err := zstd.NewWriter(nil, zstd.WithEncoderDict(h.ZstdDict)) 127 + if err != nil { 128 + log.Printf("ERROR: [CompressedWriter] Failed to create zstd encoder with dictionary: %v", err) 129 + cancel() 130 + return 131 + } 132 + defer encoder.Close() 133 + 134 + for { 135 + select { 136 + case result, ok := <-results: 137 + if !ok { 138 + return 139 + } 140 + 141 + data, err := json.Marshal(result) 142 + if err != nil { 143 + log.Printf("ERROR: [CompressedWriter] Failed to marshal JSON: %v", err) 144 + cancel() 145 + return 146 + } 147 + 148 + compressed := encoder.EncodeAll(data, nil) 149 + 150 + if err := conn.WriteMessage(websocket.BinaryMessage, compressed); err != nil { 151 + log.Printf("ERROR: [CompressedWriter] Failed to write compressed message: %v", err) 152 + cancel() 153 + return 154 + } 155 + case <-ctx.Done(): 156 + log.Printf("[CompressedWriter] Context cancelled, stopping.") 157 + return 158 + } 159 + } 160 + } 161 + 162 + func (h *BackfillHandler) writer(ctx context.Context, cancel context.CancelFunc, conn *websocket.Conn, results <-chan interface{}, done chan<- struct{}) { 163 + defer close(done) 164 + for { 165 + select { 166 + case result, ok := <-results: 167 + if !ok { 168 + return 169 + } 170 + if err := h.sendMessage(conn, result); err != nil { 171 + log.Printf("ERROR: [Writer] Failed to write message, closing connection: %v", err) 172 + cancel() 173 + return 174 + } 175 + case <-ctx.Done(): 176 + log.Printf("[Writer] Context cancelled, stopping.") 177 + return 178 + } 179 + } 180 + } 181 + 182 + func (h *BackfillHandler) producer(ctx context.Context, wg *sync.WaitGroup, jobs chan<- string, session *Session) { 183 + defer close(jobs) 184 + defer wg.Done() 185 + 186 + isFullNetwork := len(session.Params.WantedDIDs) == 1 && session.Params.WantedDIDs[0] == "*" 187 + isAllCollections := len(session.Params.WantedCollections) == 1 && session.Params.WantedCollections[0] == "*" 188 + 189 + if isFullNetwork { 190 + if isAllCollections { 191 + // --- Case 1: Full Network, All Collections (dids=*&collections=*) --- 192 + // We need to list *all* repos from the relay. 193 + log.Printf("[Producer] Starting full network scan for all collections.") 194 + for { 195 + select { 196 + case <-ctx.Done(): 197 + log.Printf("[Producer] Context cancelled, stopping full repo fetch.") 198 + return 199 + default: 200 + } 201 + 202 + log.Printf("[Producer] Fetching all repos with cursor: %s", session.ListReposCursor) 203 + repos, nextCursor, err := h.AtpClient.ListRepos(ctx, session.ListReposCursor) 204 + if err != nil { 205 + log.Printf("ERROR: [Producer] Failed to list all repos: %v", err) 206 + return 207 + } 208 + 209 + for _, repo := range repos { 210 + if !session.IsDIDComplete(repo.DID) { 211 + wg.Add(1) 212 + jobs <- repo.DID 213 + } 214 + } 215 + 216 + session.mu.Lock() 217 + session.ListReposCursor = nextCursor 218 + session.LastAccessed = time.Now() 219 + session.mu.Unlock() 220 + 221 + if nextCursor == "" { 222 + log.Printf("[Producer] Finished fetching all repos from relay.") 223 + break 224 + } 225 + } 226 + } else { 227 + // --- Case 2: Full Network, Specific Collections (dids=*&collections=a,b,c) --- 228 + // For each specific collection, page through all repos and send DIDs to workers. 229 + log.Printf("[Producer] Starting network scan for specific collections: %v", session.Params.WantedCollections) 230 + for _, collection := range session.Params.WantedCollections { 231 + for { 232 + select { 233 + case <-ctx.Done(): 234 + log.Printf("[Producer] Context cancelled, stopping repo fetch.") 235 + return 236 + default: 237 + } 238 + 239 + log.Printf("[Producer] Fetching repos for %s with cursor: %s", collection, session.ListReposCursor) 240 + repos, nextCursor, err := h.AtpClient.ListReposByCollection(ctx, collection, session.ListReposCursor) 241 + if err != nil { 242 + log.Printf("ERROR: [Producer] Failed to list repos for collection %s: %v", collection, err) 243 + return 244 + } 245 + 246 + for _, repo := range repos { 247 + if !session.IsDIDComplete(repo.DID) { 248 + wg.Add(1) 249 + jobs <- repo.DID 250 + } 251 + } 252 + 253 + session.mu.Lock() 254 + session.ListReposCursor = nextCursor 255 + session.LastAccessed = time.Now() 256 + session.mu.Unlock() 257 + 258 + if nextCursor == "" { 259 + log.Printf("[Producer] Finished fetching all repos for collection %s", collection) 260 + break 261 + } 262 + } 263 + } 264 + } 265 + } else { 266 + // --- Case 3: Specific List of DIDs (dids=a,b,c) --- 267 + // Send user-provided DIDs to workers. 268 + for _, did := range session.Params.WantedDIDs { 269 + select { 270 + case <-ctx.Done(): 271 + log.Printf("[Producer] Context cancelled, stopping DID processing.") 272 + return 273 + default: 274 + if !session.IsDIDComplete(did) { 275 + wg.Add(1) 276 + jobs <- did 277 + } else { 278 + log.Printf("[Producer] Skipping already completed DID: %s", did) 279 + } 280 + } 281 + } 282 + } 283 + } 284 + 285 + func (h *BackfillHandler) worker(ctx context.Context, id int, wg *sync.WaitGroup, jobs <-chan string, results chan<- interface{}, session *Session) { 286 + for did := range jobs { 287 + func(did string) { 288 + defer func() { 289 + wg.Done() 290 + 291 + runtime.GC() 292 + debug.FreeOSMemory() 293 + 294 + log.Printf("[Worker %d] Cleaned up resources for DID: %s", id, did) 295 + }() 296 + 297 + select { 298 + case <-ctx.Done(): 299 + return 300 + default: 301 + } 302 + 303 + log.Printf("[Worker %d] Processing DID: %s", id, did) 304 + pdsURL, err := h.AtpClient.ResolveDID(ctx, did) 305 + if err != nil { 306 + log.Printf("WARN: [Worker %d] Could not resolve DID %s, skipping. Error: %v", id, did, err) 307 + return 308 + } 309 + 310 + if h.UseGetRepoMethod { 311 + h.processDIDWithGetRepo(ctx, id, did, pdsURL, results, session) 312 + } else { 313 + h.processDIDWithListRecords(ctx, id, did, pdsURL, results, session) 314 + } 315 + 316 + session.MarkDIDComplete(did) 317 + log.Printf("[Worker %d] Finished DID: %s", id, did) 318 + }(did) 319 + } 320 + } 321 + 322 + func (h *BackfillHandler) processDIDWithGetRepo(ctx context.Context, id int, did, pdsURL string, results chan<- interface{}, session *Session) { 323 + log.Printf("[Worker %d] Using streaming getRepo method for %s", id, did) 324 + isAllCollections := len(session.Params.WantedCollections) == 1 && session.Params.WantedCollections[0] == "*" 325 + 326 + wantedSet := make(map[string]struct{}) 327 + if !isAllCollections { 328 + for _, coll := range session.Params.WantedCollections { 329 + wantedSet[coll] = struct{}{} 330 + } 331 + } 332 + 333 + respBody, err := h.AtpClient.GetRepo(ctx, pdsURL, did) 334 + if err != nil { 335 + log.Printf("WARN: [Worker %d] Failed to get repo stream for %s: %v", id, did, err) 336 + return 337 + } 338 + defer respBody.Close() 339 + 340 + if err := os.MkdirAll("./temp", 0o755); err != nil { 341 + panic(err) 342 + } 343 + tempFile, err := ioutil.TempFile("./temp", "backstream-repo-*.car") 344 + if err != nil { 345 + log.Printf("ERROR: [Worker %d] Failed to create temp file for %s: %v", id, did, err) 346 + return 347 + } 348 + defer os.Remove(tempFile.Name()) 349 + 350 + if _, err := io.Copy(tempFile, respBody); err != nil { 351 + log.Printf("ERROR: [Worker %d] Failed to write repo to temp file for %s: %v", id, did, err) 352 + return 353 + } 354 + 355 + if err := tempFile.Close(); err != nil { 356 + log.Printf("ERROR: [Worker %d] Failed to close temp file for %s: %v", id, did, err) 357 + return 358 + } 359 + 360 + readHandle, err := os.Open(tempFile.Name()) 361 + if err != nil { 362 + log.Printf("ERROR: [Worker %d] Failed to open temp file for reading %s: %v", id, did, err) 363 + return 364 + } 365 + defer readHandle.Close() 366 + 367 + _, r, err := atrepo.LoadRepoFromCAR(ctx, readHandle) 368 + if err != nil { 369 + log.Printf("WARN: [Worker %d] Failed to read CAR stream for %s from temp file: %v", id, did, err) 370 + return 371 + } 372 + 373 + err = r.MST.Walk(func(k []byte, v cid.Cid) error { 374 + select { 375 + case <-ctx.Done(): 376 + return errors.New("context cancelled during repo walk") 377 + default: 378 + } 379 + 380 + path := string(k) 381 + collection, rkey, err := syntax.ParseRepoPath(path) 382 + if err != nil { 383 + log.Printf("WARN: [Worker %d] Could not parse repo path '%s' for %s, skipping record", id, path, did) 384 + return nil 385 + } 386 + 387 + if !isAllCollections { 388 + if _, ok := wantedSet[string(collection)]; !ok { 389 + return nil 390 + } 391 + } 392 + 393 + recBytes, _, err := r.GetRecordBytes(ctx, collection, rkey) 394 + if err != nil { 395 + log.Printf("WARN: [Worker %d] Failed to get record bytes for %s: %v", id, path, err) 396 + return nil 397 + } 398 + 399 + recordVal, err := data.UnmarshalCBOR(recBytes) 400 + if err != nil { 401 + log.Printf("WARN: [Worker %d] Failed to unmarshal record CBOR for %s: %v", id, path, err) 402 + return nil 403 + } 404 + 405 + record := Record{ 406 + URI: fmt.Sprintf("at://%s/%s", did, path), 407 + CID: v.String(), 408 + Value: recordVal, 409 + } 410 + 411 + output := h.formatOutput(record, did, string(collection), session.Params.GetRecordFormat) 412 + select { 413 + case results <- output: 414 + case <-ctx.Done(): 415 + return errors.New("context cancelled while sending result") 416 + } 417 + 418 + session.SetListRecordsCursor(did, string(collection), string(rkey)) 419 + return nil 420 + }) 421 + 422 + if err != nil && !errors.Is(err, context.Canceled) { 423 + log.Printf("WARN: [Worker %d] Error while walking repo for %s: %v", id, did, err) 424 + } 425 + } 426 + 427 + func (h *BackfillHandler) processDIDWithListRecords(ctx context.Context, id int, did, pdsURL string, results chan<- interface{}, session *Session) { 428 + log.Printf("[Worker %d] Using listRecords method for %s", id, did) 429 + isAllCollections := len(session.Params.WantedCollections) == 1 && session.Params.WantedCollections[0] == "*" 430 + var collectionsToProcess []string 431 + 432 + if isAllCollections { 433 + repoCollections, err := h.AtpClient.DescribeRepo(ctx, pdsURL, did) 434 + if err != nil { 435 + log.Printf("WARN: [Worker %d] Could not describe repo for %s to find collections, skipping. Error: %v", id, did, err) 436 + return 437 + } 438 + collectionsToProcess = repoCollections 439 + log.Printf("[Worker %d] Found %d collections for DID %s", id, len(collectionsToProcess), did) 440 + } else { 441 + collectionsToProcess = session.Params.WantedCollections 442 + } 443 + 444 + for _, collection := range collectionsToProcess { 445 + cursor := session.GetListRecordsCursor(did, collection) 446 + for { 447 + select { 448 + case <-ctx.Done(): 449 + log.Printf("[Worker %d] Context cancelled for DID %s", id, did) 450 + return 451 + default: 452 + } 453 + 454 + records, nextCursor, err := h.AtpClient.ListRecords(ctx, pdsURL, did, collection, cursor) 455 + if err != nil { 456 + if !strings.Contains(err.Error(), "status: 400") { 457 + log.Printf("WARN: [Worker %d] Failed to list records for %s/%s, skipping. Error: %v", id, did, collection, err) 458 + } 459 + break 460 + } 461 + 462 + for _, record := range records { 463 + output := h.formatOutput(record, did, collection, session.Params.GetRecordFormat) 464 + select { 465 + case results <- output: 466 + case <-ctx.Done(): 467 + log.Printf("[Worker %d] Context cancelled while sending results for %s", id, did) 468 + return 469 + } 470 + } 471 + 472 + session.SetListRecordsCursor(did, collection, nextCursor) 473 + cursor = nextCursor 474 + if cursor == "" { 475 + break 476 + } 477 + } 478 + } 479 + } 480 + 481 + func (h *BackfillHandler) parseQueryParams(r *http.Request) (BackfillParams, string, error) { 482 + query := r.URL.Query() 483 + ticket := query.Get("ticket") 484 + 485 + wantedDidsStr := query.Get("wantedDids") 486 + wantedCollectionsStr := query.Get("wantedCollections") 487 + 488 + if wantedCollectionsStr == "" && wantedDidsStr == "" && ticket == "" { 489 + ticket = "jetstreamfalse" 490 + } else if ticket == "" { 491 + ticket = generateTicket() 492 + } 493 + 494 + if wantedDidsStr == "" { 495 + log.Println("Query parameter 'wantedDids' not specified, defaulting to '*' (all repos).") 496 + wantedDidsStr = "*" 497 + } 498 + 499 + if wantedCollectionsStr == "" { 500 + log.Println("Query parameter 'wantedCollections' not specified, defaulting to '*' (all collections).") 501 + wantedCollectionsStr = "*" 502 + } 503 + 504 + params := BackfillParams{ 505 + WantedDIDs: strings.Split(wantedDidsStr, ","), 506 + WantedCollections: strings.Split(wantedCollectionsStr, ","), 507 + GetRecordFormat: query.Get("getRecordFormat") == "true", 508 + } 509 + return params, ticket, nil 510 + } 511 + 512 + func (h *BackfillHandler) formatOutput(record Record, did, collection string, getRecordFormat bool) interface{} { 513 + if getRecordFormat { 514 + return GetRecordOutput{ 515 + URI: record.URI, 516 + CID: record.CID, 517 + Value: record.Value, 518 + } 519 + } 520 + uriParts := strings.Split(record.URI, "/") 521 + rkey := "" 522 + if len(uriParts) == 5 { 523 + rkey = uriParts[4] 524 + } 525 + return JetstreamLikeOutput{ 526 + Did: did, 527 + Kind: "commit", 528 + TimeUS: "1725911162329308", 529 + Commit: JetstreamLikeCommit{ 530 + Rev: rkey, 531 + Operation: "create", 532 + Collection: collection, 533 + RKey: rkey, 534 + Record: record.Value, 535 + CID: record.CID, 536 + }, 537 + } 538 + } 539 + 540 + func (h *BackfillHandler) sendError(conn *websocket.Conn, message string) { 541 + log.Printf("Sending error to client: %s", message) 542 + _ = conn.WriteJSON(map[string]string{"error": message}) 543 + } 544 + 545 + func (h *BackfillHandler) sendMessage(conn *websocket.Conn, v interface{}) error { 546 + return conn.WriteJSON(v) 547 + }
+113
backstream/session.go
··· 1 + package backstream 2 + 3 + import ( 4 + "crypto/rand" 5 + "encoding/hex" 6 + "log" 7 + "sync" 8 + "time" 9 + ) 10 + 11 + type Session struct { 12 + Ticket string 13 + Params BackfillParams 14 + LastAccessed time.Time 15 + 16 + ListReposCursor string // Cursor for listReposByCollection if wantedDids=* 17 + CompletedDIDs map[string]bool // Set of DIDs that have been fully processed. 18 + listRecordsCursors map[string]string // Key: "did/collection", Value: cursor 19 + 20 + mu sync.Mutex 21 + } 22 + 23 + func (s *Session) GetListRecordsCursor(did, collection string) string { 24 + s.mu.Lock() 25 + defer s.mu.Unlock() 26 + key := did + "/" + collection 27 + return s.listRecordsCursors[key] 28 + } 29 + 30 + func (s *Session) SetListRecordsCursor(did, collection, cursor string) { 31 + s.mu.Lock() 32 + defer s.mu.Unlock() 33 + key := did + "/" + collection 34 + s.listRecordsCursors[key] = cursor 35 + s.LastAccessed = time.Now() 36 + } 37 + 38 + func (s *Session) MarkDIDComplete(did string) { 39 + s.mu.Lock() 40 + defer s.mu.Unlock() 41 + s.CompletedDIDs[did] = true 42 + s.LastAccessed = time.Now() 43 + } 44 + 45 + func (s *Session) IsDIDComplete(did string) bool { 46 + s.mu.Lock() 47 + defer s.mu.Unlock() 48 + return s.CompletedDIDs[did] 49 + } 50 + 51 + type SessionManager struct { 52 + sessions map[string]*Session 53 + ttl time.Duration 54 + mu sync.Mutex 55 + } 56 + 57 + func NewSessionManager(ttl time.Duration) *SessionManager { 58 + sm := &SessionManager{ 59 + sessions: make(map[string]*Session), 60 + ttl: ttl, 61 + } 62 + go sm.cleanupLoop() 63 + return sm 64 + } 65 + 66 + func (sm *SessionManager) GetOrCreate(ticket string, params BackfillParams) *Session { 67 + sm.mu.Lock() 68 + defer sm.mu.Unlock() 69 + 70 + if session, exists := sm.sessions[ticket]; exists { 71 + log.Printf("Resuming existing session for ticket: %s", ticket) 72 + session.LastAccessed = time.Now() 73 + if session.CompletedDIDs == nil { 74 + session.CompletedDIDs = make(map[string]bool) 75 + } 76 + return session 77 + } 78 + 79 + log.Printf("Creating new session for ticket: %s", ticket) 80 + newSession := &Session{ 81 + Ticket: ticket, 82 + Params: params, 83 + LastAccessed: time.Now(), 84 + listRecordsCursors: make(map[string]string), 85 + CompletedDIDs: make(map[string]bool), 86 + } 87 + sm.sessions[ticket] = newSession 88 + return newSession 89 + } 90 + 91 + func (sm *SessionManager) cleanupLoop() { 92 + ticker := time.NewTicker(sm.ttl / 2) 93 + defer ticker.Stop() 94 + for range ticker.C { 95 + sm.mu.Lock() 96 + now := time.Now() 97 + for ticket, session := range sm.sessions { 98 + if now.Sub(session.LastAccessed) > sm.ttl { 99 + log.Printf("Session %s expired. Cleaning up.", ticket) 100 + delete(sm.sessions, ticket) 101 + } 102 + } 103 + sm.mu.Unlock() 104 + } 105 + } 106 + 107 + func generateTicket() string { 108 + bytes := make([]byte, 16) 109 + if _, err := rand.Read(bytes); err != nil { 110 + return "fallback-ticket-" + time.Now().String() 111 + } 112 + return hex.EncodeToString(bytes) 113 + }
cmd/backstream/jetstream-zstd-dict.bin

This is a binary file and will not be displayed.

+77
cmd/backstream/main.go
··· 1 + package main 2 + 3 + import ( 4 + "fmt" 5 + "log" 6 + "net/http" 7 + "os" 8 + "runtime/debug" 9 + "time" 10 + 11 + "tangled.org/whey.party/red-dwarf-server/backstream" 12 + 13 + _ "net/http/pprof" 14 + ) 15 + 16 + const ( 17 + defaultRelay = "https://relay1.us-west.bsky.network" 18 + 19 + plcDirectory = "https://plc.directory" 20 + 21 + zstdDictionaryPath = "jetstream-zstd-dict.bin" 22 + 23 + useGetRepoMethod = true 24 + ) 25 + 26 + func rootHandler(w http.ResponseWriter, r *http.Request) { 27 + w.Header().Set("Content-Type", "text/plain; charset=utf-8") 28 + fmt.Fprint(w, `Welcome to Backstream`) 29 + } 30 + 31 + func main() { 32 + debug.SetMemoryLimit(2 << 30) // 2 GB 33 + go func() { 34 + log.Println(http.ListenAndServe("localhost:6060", nil)) 35 + }() 36 + log.Println("Starting Bluesky Backfill Service...") 37 + if useGetRepoMethod { 38 + log.Println("INFO: Using efficient 'getRepo' CAR file method for backfills.") 39 + } else { 40 + log.Println("INFO: Using 'listRecords' pagination method for backfills.") 41 + } 42 + 43 + zstdDict, err := os.ReadFile(zstdDictionaryPath) 44 + if err != nil { 45 + log.Printf("WARN: Could not read zstd dictionary file '%s': %v", zstdDictionaryPath, err) 46 + log.Println("WARN: Zstd compression will be unavailable.") 47 + } else { 48 + log.Printf("Successfully loaded zstd dictionary (%d bytes).", len(zstdDict)) 49 + } 50 + 51 + sessionManager := backstream.NewSessionManager(5 * time.Minute) 52 + 53 + atpClient := backstream.NewATProtoClient(defaultRelay, plcDirectory) 54 + 55 + backfillHandler := &backstream.BackfillHandler{ 56 + Upgrader: backstream.DefaultUpgrader, 57 + SessionManager: sessionManager, 58 + AtpClient: atpClient, 59 + ZstdDict: zstdDict, 60 + UseGetRepoMethod: useGetRepoMethod, 61 + } 62 + 63 + http.Handle("/subscribe", backfillHandler) 64 + 65 + http.HandleFunc("/", rootHandler) 66 + 67 + log.Println("Server listening on :3877") 68 + log.Println("Connect via WebSocket to: ws://localhost:3877/subscribe?wantedCollections=app.bsky.feed.post&wantedDids=*") 69 + log.Println("---") 70 + log.Println("Example with specific DIDs: ws://localhost:3877/subscribe?wantedCollections=app.bsky.feed.post&wantedDids=did:plc:abc,did:plc:xyz") 71 + log.Println("Example with ticket for resumable session: ws://localhost:3877/subscribe?wantedCollections=app.bsky.feed.post&wantedDids=*&ticket=my-session-123") 72 + log.Println("Example with alternative output format: ws://localhost:3877/subscribe?wantedCollections=app.bsky.feed.post&wantedDids=*&getRecordFormat=true") 73 + 74 + if err := http.ListenAndServe(":3877", nil); err != nil { 75 + log.Fatalf("Failed to start server: %v", err) 76 + } 77 + }
+26
cmd/jetrelay/main.go
··· 1 + package main 2 + 3 + import ( 4 + "flag" 5 + "fmt" 6 + ) 7 + 8 + type multiFlag []string 9 + 10 + func (m *multiFlag) String() string { 11 + return fmt.Sprint(*m) 12 + } 13 + 14 + func (m *multiFlag) Set(value string) error { 15 + *m = append(*m, value) 16 + return nil 17 + } 18 + 19 + func main() { 20 + var js multiFlag 21 + flag.Var(&js, "j", "jetstream instances 'write multiple to input more than one'") 22 + 23 + flag.Parse() 24 + 25 + fmt.Println(js) // prints: [hi hello what] 26 + }
+34 -6
go.mod
··· 5 5 require ( 6 6 github.com/bluesky-social/indigo v0.0.0-20251202051123-81f317e322bc 7 7 github.com/ericvolp12/jwt-go-secp256k1 v0.0.2 8 + github.com/gin-contrib/cors v1.7.6 8 9 github.com/gin-gonic/gin v1.11.0 9 10 github.com/golang-jwt/jwt v3.2.2+incompatible 10 11 github.com/google/uuid v1.6.0 11 12 github.com/gorilla/websocket v1.5.3 12 13 github.com/hashicorp/golang-lru/arc/v2 v2.0.7 14 + github.com/klauspost/compress v1.18.2 13 15 github.com/prometheus/client_golang v1.23.2 14 16 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b 15 17 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 ··· 18 20 ) 19 21 20 22 require ( 21 - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect 22 - github.com/gin-contrib/cors v1.7.6 // indirect 23 + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect 24 + github.com/gogo/protobuf v1.3.2 // indirect 25 + github.com/hashicorp/golang-lru v1.0.2 // indirect 26 + github.com/ipfs/bbloom v0.0.4 // indirect 27 + github.com/ipfs/go-block-format v0.2.0 // indirect 28 + github.com/ipfs/go-blockservice v0.5.2 // indirect 29 + github.com/ipfs/go-datastore v0.6.0 // indirect 30 + github.com/ipfs/go-ipfs-blockstore v1.3.1 // indirect 31 + github.com/ipfs/go-ipfs-ds-help v1.1.1 // indirect 32 + github.com/ipfs/go-ipfs-exchange-interface v0.2.1 // indirect 33 + github.com/ipfs/go-ipfs-util v0.0.3 // indirect 34 + github.com/ipfs/go-ipld-cbor v0.1.0 // indirect 35 + github.com/ipfs/go-ipld-format v0.6.0 // indirect 36 + github.com/ipfs/go-ipld-legacy v0.2.1 // indirect 37 + github.com/ipfs/go-log v1.0.5 // indirect 38 + github.com/ipfs/go-log/v2 v2.5.1 // indirect 39 + github.com/ipfs/go-merkledag v0.11.0 // indirect 40 + github.com/ipfs/go-metrics-interface v0.0.1 // indirect 41 + github.com/ipfs/go-verifcid v0.0.3 // indirect 42 + github.com/ipld/go-car v0.6.1-0.20230509095817-92d28eb23ba4 // indirect 43 + github.com/ipld/go-codec-dagpb v1.6.0 // indirect 44 + github.com/ipld/go-ipld-prime v0.21.0 // indirect 45 + github.com/jbenet/goprocess v0.1.4 // indirect 23 46 github.com/lestrrat-go/blackmagic v1.0.1 // indirect 24 47 github.com/lestrrat-go/httpcc v1.0.1 // indirect 25 48 github.com/lestrrat-go/httprc v1.0.4 // indirect 26 49 github.com/lestrrat-go/iter v1.0.2 // indirect 27 50 github.com/lestrrat-go/jwx/v2 v2.0.12 // indirect 28 51 github.com/lestrrat-go/option v1.0.1 // indirect 52 + github.com/opentracing/opentracing-go v1.2.0 // indirect 53 + github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f // indirect 29 54 github.com/segmentio/asm v1.2.0 // indirect 55 + go.uber.org/atomic v1.11.0 // indirect 56 + go.uber.org/multierr v1.11.0 // indirect 57 + go.uber.org/zap v1.26.0 // indirect 30 58 ) 31 59 32 60 require ( ··· 47 75 github.com/goccy/go-json v0.10.5 // indirect 48 76 github.com/goccy/go-yaml v1.18.0 // indirect 49 77 github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect 50 - github.com/ipfs/go-cid v0.4.1 // indirect 78 + github.com/ipfs/go-cid v0.5.0 51 79 github.com/json-iterator/go v1.1.12 // indirect 52 80 github.com/klauspost/cpuid/v2 v2.3.0 // indirect 53 81 github.com/leodido/go-urn v1.4.0 // indirect ··· 71 99 github.com/spaolacci/murmur3 v1.1.0 // indirect 72 100 github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 73 101 github.com/ugorji/go/codec v1.3.0 // indirect 74 - github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e // indirect 102 + github.com/whyrusleeping/cbor-gen v0.3.1 // indirect 75 103 github.com/whyrusleeping/go-did v0.0.0-20240828165449-bcaa7ae21371 76 104 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect 77 105 go.opentelemetry.io/auto/sdk v1.1.0 // indirect ··· 87 115 golang.org/x/sys v0.35.0 // indirect 88 116 golang.org/x/text v0.28.0 // indirect 89 117 golang.org/x/tools v0.35.0 // indirect 90 - golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect 118 + golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect 91 119 google.golang.org/protobuf v1.36.9 // indirect 92 - lukechampine.com/blake3 v1.2.1 // indirect 120 + lukechampine.com/blake3 v1.4.1 // indirect 93 121 )
+215 -12
go.sum
··· 1 + github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 + github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 3 + github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= 4 + github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 1 5 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 2 6 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 3 7 github.com/bluesky-social/indigo v0.0.0-20251202051123-81f317e322bc h1:2t+uAvfzJiCsTMwn5fW85t/IGa0+2I7BXS2ORastK4o= ··· 10 14 github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 11 15 github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= 12 16 github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= 17 + github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 18 + github.com/cskr/pubsub v1.0.2 h1:vlOzMhl6PFn60gRlTQQsIfVwaPB/B/8MziK8FhEPt/0= 19 + github.com/cskr/pubsub v1.0.2/go.mod h1:/8MzYXk/NJAz782G8RPkFzXTZVu63VotefPnR9TIRis= 13 20 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 14 21 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 15 22 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 16 23 github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= 17 - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= 18 24 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= 25 + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= 26 + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= 19 27 github.com/earthboundkid/versioninfo/v2 v2.24.1 h1:SJTMHaoUx3GzjjnUO1QzP3ZXK6Ee/nbWyCm58eY3oUg= 20 28 github.com/earthboundkid/versioninfo/v2 v2.24.1/go.mod h1:VcWEooDEuyUJnMfbdTh0uFN4cfEIg+kHMuWB2CDCLjw= 21 29 github.com/ericvolp12/jwt-go-secp256k1 v0.0.2 h1:puGwrNTY2vCt8eakkSEq2yeNxUD3zb2kPhv1OsF1hPs= 22 30 github.com/ericvolp12/jwt-go-secp256k1 v0.0.2/go.mod h1:ntxzdN7EhBp8h+N78AtN2hjbVKHa7mijryYd9nPMyMo= 23 31 github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 24 32 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 25 - github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= 26 - github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= 33 + github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= 34 + github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 27 35 github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= 28 36 github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= 29 37 github.com/gin-contrib/cors v1.7.6 h1:3gQ8GMzs1Ylpf70y8bMw4fVpycXIeX1ZemuSQIsnQQY= ··· 45 53 github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 46 54 github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= 47 55 github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= 48 - github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 56 + github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= 49 57 github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 50 58 github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= 51 59 github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= 52 60 github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= 53 61 github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= 62 + github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 63 + github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 54 64 github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= 55 65 github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= 56 66 github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 57 67 github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 58 68 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 69 + github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= 70 + github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= 71 + github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 59 72 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 60 73 github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 74 + github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= 75 + github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 61 76 github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= 62 77 github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 78 + github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= 79 + github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 63 80 github.com/hashicorp/golang-lru/arc/v2 v2.0.7 h1:QxkVTxwColcduO+LP7eJO56r2hFiG8zEbfAAzRv52KQ= 64 81 github.com/hashicorp/golang-lru/arc/v2 v2.0.7/go.mod h1:Pe7gBlGdc8clY5LJ0LpJXMt5AmgmWNH1g+oFFVUHOEc= 65 82 github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= 66 83 github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= 67 - github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= 68 - github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk= 84 + github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ= 85 + github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= 86 + github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= 87 + github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= 88 + github.com/ipfs/go-bitswap v0.11.0 h1:j1WVvhDX1yhG32NTC9xfxnqycqYIlhzEzLXG/cU1HyQ= 89 + github.com/ipfs/go-bitswap v0.11.0/go.mod h1:05aE8H3XOU+LXpTedeAS0OZpcO1WFsj5niYQH9a1Tmk= 90 + github.com/ipfs/go-block-format v0.2.0 h1:ZqrkxBA2ICbDRbK8KJs/u0O3dlp6gmAuuXUJNiW1Ycs= 91 + github.com/ipfs/go-block-format v0.2.0/go.mod h1:+jpL11nFx5A/SPpsoBn6Bzkra/zaArfSmsknbPMYgzM= 92 + github.com/ipfs/go-blockservice v0.5.2 h1:in9Bc+QcXwd1apOVM7Un9t8tixPKdaHQFdLSUM1Xgk8= 93 + github.com/ipfs/go-blockservice v0.5.2/go.mod h1:VpMblFEqG67A/H2sHKAemeH9vlURVavlysbdUI632yk= 94 + github.com/ipfs/go-cid v0.5.0 h1:goEKKhaGm0ul11IHA7I6p1GmKz8kEYniqFopaB5Otwg= 95 + github.com/ipfs/go-cid v0.5.0/go.mod h1:0L7vmeNXpQpUS9vt+yEARkJ8rOg43DF3iPgn4GIN0mk= 96 + github.com/ipfs/go-datastore v0.6.0 h1:JKyz+Gvz1QEZw0LsX1IBn+JFCJQH4SJVFtM4uWU0Myk= 97 + github.com/ipfs/go-datastore v0.6.0/go.mod h1:rt5M3nNbSO/8q1t4LNkLyUwRs8HupMeN/8O4Vn9YAT8= 98 + github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk= 99 + github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps= 100 + github.com/ipfs/go-ipfs-blockstore v1.3.1 h1:cEI9ci7V0sRNivqaOr0elDsamxXFxJMMMy7PTTDQNsQ= 101 + github.com/ipfs/go-ipfs-blockstore v1.3.1/go.mod h1:KgtZyc9fq+P2xJUiCAzbRdhhqJHvsw8u2Dlqy2MyRTE= 102 + github.com/ipfs/go-ipfs-blocksutil v0.0.1 h1:Eh/H4pc1hsvhzsQoMEP3Bke/aW5P5rVM1IWFJMcGIPQ= 103 + github.com/ipfs/go-ipfs-blocksutil v0.0.1/go.mod h1:Yq4M86uIOmxmGPUHv/uI7uKqZNtLb449gwKqXjIsnRk= 104 + github.com/ipfs/go-ipfs-delay v0.0.1 h1:r/UXYyRcddO6thwOnhiznIAiSvxMECGgtv35Xs1IeRQ= 105 + github.com/ipfs/go-ipfs-delay v0.0.1/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= 106 + github.com/ipfs/go-ipfs-ds-help v1.1.1 h1:B5UJOH52IbcfS56+Ul+sv8jnIV10lbjLF5eOO0C66Nw= 107 + github.com/ipfs/go-ipfs-ds-help v1.1.1/go.mod h1:75vrVCkSdSFidJscs8n4W+77AtTpCIAdDGAwjitJMIo= 108 + github.com/ipfs/go-ipfs-exchange-interface v0.2.1 h1:jMzo2VhLKSHbVe+mHNzYgs95n0+t0Q69GQ5WhRDZV/s= 109 + github.com/ipfs/go-ipfs-exchange-interface v0.2.1/go.mod h1:MUsYn6rKbG6CTtsDp+lKJPmVt3ZrCViNyH3rfPGsZ2E= 110 + github.com/ipfs/go-ipfs-exchange-offline v0.3.0 h1:c/Dg8GDPzixGd0MC8Jh6mjOwU57uYokgWRFidfvEkuA= 111 + github.com/ipfs/go-ipfs-exchange-offline v0.3.0/go.mod h1:MOdJ9DChbb5u37M1IcbrRB02e++Z7521fMxqCNRrz9s= 112 + github.com/ipfs/go-ipfs-pq v0.0.2 h1:e1vOOW6MuOwG2lqxcLA+wEn93i/9laCY8sXAw76jFOY= 113 + github.com/ipfs/go-ipfs-pq v0.0.2/go.mod h1:LWIqQpqfRG3fNc5XsnIhz/wQ2XXGyugQwls7BgUmUfY= 114 + github.com/ipfs/go-ipfs-routing v0.3.0 h1:9W/W3N+g+y4ZDeffSgqhgo7BsBSJwPMcyssET9OWevc= 115 + github.com/ipfs/go-ipfs-routing v0.3.0/go.mod h1:dKqtTFIql7e1zYsEuWLyuOU+E0WJWW8JjbTPLParDWo= 116 + github.com/ipfs/go-ipfs-util v0.0.3 h1:2RFdGez6bu2ZlZdI+rWfIdbQb1KudQp3VGwPtdNCmE0= 117 + github.com/ipfs/go-ipfs-util v0.0.3/go.mod h1:LHzG1a0Ig4G+iZ26UUOMjHd+lfM84LZCrn17xAKWBvs= 118 + github.com/ipfs/go-ipld-cbor v0.1.0 h1:dx0nS0kILVivGhfWuB6dUpMa/LAwElHPw1yOGYopoYs= 119 + github.com/ipfs/go-ipld-cbor v0.1.0/go.mod h1:U2aYlmVrJr2wsUBU67K4KgepApSZddGRDWBYR0H4sCk= 120 + github.com/ipfs/go-ipld-format v0.6.0 h1:VEJlA2kQ3LqFSIm5Vu6eIlSxD/Ze90xtc4Meten1F5U= 121 + github.com/ipfs/go-ipld-format v0.6.0/go.mod h1:g4QVMTn3marU3qXchwjpKPKgJv+zF+OlaKMyhJ4LHPg= 122 + github.com/ipfs/go-ipld-legacy v0.2.1 h1:mDFtrBpmU7b//LzLSypVrXsD8QxkEWxu5qVxN99/+tk= 123 + github.com/ipfs/go-ipld-legacy v0.2.1/go.mod h1:782MOUghNzMO2DER0FlBR94mllfdCJCkTtDtPM51otM= 124 + github.com/ipfs/go-log v1.0.5 h1:2dOuUCB1Z7uoczMWgAyDck5JLb72zHzrMnGnCNNbvY8= 125 + github.com/ipfs/go-log v1.0.5/go.mod h1:j0b8ZoR+7+R99LD9jZ6+AJsrzkPbSXbZfGakb5JPtIo= 126 + github.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g= 127 + github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY= 128 + github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI= 129 + github.com/ipfs/go-merkledag v0.11.0 h1:DgzwK5hprESOzS4O1t/wi6JDpyVQdvm9Bs59N/jqfBY= 130 + github.com/ipfs/go-merkledag v0.11.0/go.mod h1:Q4f/1ezvBiJV0YCIXvt51W/9/kqJGH4I1LsA7+djsM4= 131 + github.com/ipfs/go-metrics-interface v0.0.1 h1:j+cpbjYvu4R8zbleSs36gvB7jR+wsL2fGD6n0jO4kdg= 132 + github.com/ipfs/go-metrics-interface v0.0.1/go.mod h1:6s6euYU4zowdslK0GKHmqaIZ3j/b/tL7HTWtJ4VPgWY= 133 + github.com/ipfs/go-peertaskqueue v0.8.0 h1:JyNO144tfu9bx6Hpo119zvbEL9iQ760FHOiJYsUjqaU= 134 + github.com/ipfs/go-peertaskqueue v0.8.0/go.mod h1:cz8hEnnARq4Du5TGqiWKgMr/BOSQ5XOgMOh1K5YYKKM= 135 + github.com/ipfs/go-verifcid v0.0.3 h1:gmRKccqhWDocCRkC+a59g5QW7uJw5bpX9HWBevXa0zs= 136 + github.com/ipfs/go-verifcid v0.0.3/go.mod h1:gcCtGniVzelKrbk9ooUSX/pM3xlH73fZZJDzQJRvOUw= 137 + github.com/ipld/go-car v0.6.1-0.20230509095817-92d28eb23ba4 h1:oFo19cBmcP0Cmg3XXbrr0V/c+xU9U1huEZp8+OgBzdI= 138 + github.com/ipld/go-car v0.6.1-0.20230509095817-92d28eb23ba4/go.mod h1:6nkFF8OmR5wLKBzRKi7/YFJpyYR7+oEn1DX+mMWnlLA= 139 + github.com/ipld/go-codec-dagpb v1.6.0 h1:9nYazfyu9B1p3NAgfVdpRco3Fs2nFC72DqVsMj6rOcc= 140 + github.com/ipld/go-codec-dagpb v1.6.0/go.mod h1:ANzFhfP2uMJxRBr8CE+WQWs5UsNa0pYtmKZ+agnUw9s= 141 + github.com/ipld/go-ipld-prime v0.21.0 h1:n4JmcpOlPDIxBcY037SVfpd1G+Sj1nKZah0m6QH9C2E= 142 + github.com/ipld/go-ipld-prime v0.21.0/go.mod h1:3RLqy//ERg/y5oShXXdx5YIp50cFGOanyMctpPjsvxQ= 143 + github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= 144 + github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= 145 + github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA= 146 + github.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o= 147 + github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= 69 148 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 70 149 github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 150 + github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 151 + github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 152 + github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 153 + github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 154 + github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= 155 + github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= 71 156 github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= 72 157 github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= 158 + github.com/koron/go-ssdp v0.0.3 h1:JivLMY45N76b4p/vsWGOKewBQu6uf39y8l+AQ7sDKx8= 159 + github.com/koron/go-ssdp v0.0.3/go.mod h1:b2MxI6yh02pKrsyNoQUsk4+YNikaGhe4894J+Q5lDvA= 160 + github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 73 161 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 74 162 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 163 + github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 164 + github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 75 165 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 76 166 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 77 167 github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= ··· 89 179 github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= 90 180 github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= 91 181 github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= 182 + github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= 183 + github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= 184 + github.com/libp2p/go-cidranger v1.1.0 h1:ewPN8EZ0dd1LSnrtuwd4709PXVcITVeuwbag38yPW7c= 185 + github.com/libp2p/go-cidranger v1.1.0/go.mod h1:KWZTfSr+r9qEo9OkI9/SIEeAtw+NNoU0dXIXt15Okic= 186 + github.com/libp2p/go-libp2p v0.22.0 h1:2Tce0kHOp5zASFKJbNzRElvh0iZwdtG5uZheNW8chIw= 187 + github.com/libp2p/go-libp2p v0.22.0/go.mod h1:UDolmweypBSjQb2f7xutPnwZ/fxioLbMBxSjRksxxU4= 188 + github.com/libp2p/go-libp2p-asn-util v0.2.0 h1:rg3+Os8jbnO5DxkC7K/Utdi+DkY3q/d1/1q+8WeNAsw= 189 + github.com/libp2p/go-libp2p-asn-util v0.2.0/go.mod h1:WoaWxbHKBymSN41hWSq/lGKJEca7TNm58+gGJi2WsLI= 190 + github.com/libp2p/go-libp2p-record v0.2.0 h1:oiNUOCWno2BFuxt3my4i1frNrt7PerzB3queqa1NkQ0= 191 + github.com/libp2p/go-libp2p-record v0.2.0/go.mod h1:I+3zMkvvg5m2OcSdoL0KPljyJyvNDFGKX7QdlpYUcwk= 192 + github.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA= 193 + github.com/libp2p/go-libp2p-testing v0.12.0/go.mod h1:KcGDRXyN7sQCllucn1cOOS+Dmm7ujhfEyXQL5lvkcPg= 194 + github.com/libp2p/go-msgio v0.2.0 h1:W6shmB+FeynDrUVl2dgFQvzfBZcXiyqY4VmpQLu9FqU= 195 + github.com/libp2p/go-msgio v0.2.0/go.mod h1:dBVM1gW3Jk9XqHkU4eKdGvVHdLa51hoGfll6jMJMSlY= 196 + github.com/libp2p/go-nat v0.1.0 h1:MfVsH6DLcpa04Xr+p8hmVRG4juse0s3J8HyNWYHffXg= 197 + github.com/libp2p/go-nat v0.1.0/go.mod h1:X7teVkwRHNInVNWQiO/tAiAVRwSr5zoRz4YSTC3uRBM= 198 + github.com/libp2p/go-netroute v0.2.0 h1:0FpsbsvuSnAhXFnCY0VLFbJOzaK0VnP0r1QT/o4nWRE= 199 + github.com/libp2p/go-netroute v0.2.0/go.mod h1:Vio7LTzZ+6hoT4CMZi5/6CpY3Snzh2vgZhWgxMNwlQI= 200 + github.com/libp2p/go-openssl v0.1.0 h1:LBkKEcUv6vtZIQLVTegAil8jbNpJErQ9AnT+bWV+Ooo= 201 + github.com/libp2p/go-openssl v0.1.0/go.mod h1:OiOxwPpL3n4xlenjx2h7AwSGaFSC/KZvf6gNdOBQMtc= 202 + github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 92 203 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 93 204 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 205 + github.com/mattn/go-pointer v0.0.1 h1:n+XhsuGeVO6MEAp7xyEukFINEa+Quek5psIR/ylA6o0= 206 + github.com/mattn/go-pointer v0.0.1/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc= 207 + github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= 208 + github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= 94 209 github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= 95 210 github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= 96 211 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= ··· 104 219 github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= 105 220 github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= 106 221 github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= 222 + github.com/multiformats/go-multiaddr v0.7.0 h1:gskHcdaCyPtp9XskVwtvEeQOG465sCohbQIirSyqxrc= 223 + github.com/multiformats/go-multiaddr v0.7.0/go.mod h1:Fs50eBDWvZu+l3/9S6xAE7ZYj6yhxlvaVZjakWN7xRs= 224 + github.com/multiformats/go-multiaddr-dns v0.3.1 h1:QgQgR+LQVt3NPTjbrLLpsaT2ufAA2y0Mkk+QRVJbW3A= 225 + github.com/multiformats/go-multiaddr-dns v0.3.1/go.mod h1:G/245BRQ6FJGmryJCrOuTdB37AMA5AMOVuO6NY3JwTk= 226 + github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E= 227 + github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo= 107 228 github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= 108 229 github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= 230 + github.com/multiformats/go-multicodec v0.9.0 h1:pb/dlPnzee/Sxv/j4PmkDRxCOi3hXTz3IbPKOXWJkmg= 231 + github.com/multiformats/go-multicodec v0.9.0/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k= 109 232 github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U= 110 233 github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= 234 + github.com/multiformats/go-multistream v0.3.3 h1:d5PZpjwRgVlbwfdTDjife7XszfZd8KYWfROYFlGcR8o= 235 + github.com/multiformats/go-multistream v0.3.3/go.mod h1:ODRoqamLUsETKS9BNcII4gcRsJBU5VAwRIv7O39cEXg= 111 236 github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= 112 237 github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= 113 238 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 114 239 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 240 + github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= 241 + github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= 115 242 github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= 116 243 github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= 244 + github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 117 245 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 118 246 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 247 + github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f h1:VXTQfuJj9vKR4TCkEuWIckKvdHFeJH/huIFJ9/cXOB0= 248 + github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw= 119 249 github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= 120 250 github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= 121 251 github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= ··· 128 258 github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= 129 259 github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= 130 260 github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= 261 + github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 131 262 github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= 132 263 github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= 264 + github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 133 265 github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= 134 266 github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= 267 + github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 268 + github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= 269 + github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= 270 + github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= 271 + github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= 272 + github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 h1:RC6RW7j+1+HkWaX/Yh71Ee5ZHaHYt7ZP4sQgUrm6cDU= 273 + github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572/go.mod h1:w0SWMsp6j9O/dk4/ZpIhL+3CkG8ofA2vuv7k+ltqUMc= 135 274 github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= 136 275 github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 137 276 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 138 277 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 139 278 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 140 279 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 280 + github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 141 281 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 282 + github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 142 283 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 143 284 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 144 285 github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= ··· 149 290 github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 150 291 github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= 151 292 github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= 152 - github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e h1:28X54ciEwwUxyHn9yrZfl5ojgF4CBNLWX7LR0rvBkf4= 153 - github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so= 293 + github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 294 + github.com/warpfork/go-testmark v0.12.1 h1:rMgCpJfwy1sJ50x0M0NgyphxYYPMOODIJHhsXyEHU0s= 295 + github.com/warpfork/go-testmark v0.12.1/go.mod h1:kHwy7wfvGSPh1rQJYKayD4AbtNaeyZdcGi9tNJTaa5Y= 296 + github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ= 297 + github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= 298 + github.com/whyrusleeping/cbor-gen v0.3.1 h1:82ioxmhEYut7LBVGhGq8xoRkXPLElVuh5mV67AFfdv0= 299 + github.com/whyrusleeping/cbor-gen v0.3.1/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so= 154 300 github.com/whyrusleeping/go-did v0.0.0-20240828165449-bcaa7ae21371 h1:W4jEGWdes35iuiiAYNZFOjx+dwzQOBh33kVpc0C0YiE= 155 301 github.com/whyrusleeping/go-did v0.0.0-20240828165449-bcaa7ae21371/go.mod h1:39U9RRVr4CKbXpXYopWn+FSH5s+vWu6+RmguSPWAq5s= 302 + github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 303 + github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 304 + github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 156 305 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 157 306 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b h1:CzigHMRySiX3drau9C6Q5CAbNIApmLdat5jPMqChvDA= 158 307 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b/go.mod h1:/y/V339mxv2sZmYYR64O07VuCpdNZqCTwO8ZcouTMI8= ··· 172 321 go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= 173 322 go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= 174 323 go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= 324 + go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 325 + go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 326 + go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= 327 + go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 328 + go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= 175 329 go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 176 330 go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 177 331 go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= 178 332 go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= 333 + go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= 334 + go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 335 + go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 336 + go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 337 + go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= 338 + go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= 339 + go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= 340 + go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= 341 + go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= 179 342 go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= 180 343 go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= 181 344 golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= 182 345 golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= 183 346 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 347 + golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 348 + golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 349 + golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 184 350 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 185 351 golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= 186 352 golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= 187 353 golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= 354 + golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 355 + golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 356 + golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 357 + golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 358 + golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 188 359 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 189 360 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 190 361 golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= 191 362 golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= 363 + golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 364 + golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 192 365 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 366 + golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 367 + golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 193 368 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 369 + golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 194 370 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 195 371 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 196 372 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 197 373 golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= 198 374 golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= 199 375 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 376 + golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 377 + golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 378 + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 200 379 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 201 380 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 202 381 golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= 203 382 golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 204 383 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 384 + golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 385 + golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 205 386 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 387 + golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 388 + golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 206 389 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 390 + golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 207 391 golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 208 392 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 209 393 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= ··· 229 413 golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= 230 414 golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= 231 415 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 416 + golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 417 + golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 418 + golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 419 + golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 420 + golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 232 421 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 422 + golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 423 + golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 424 + golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 233 425 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 234 426 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 235 427 golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= 236 428 golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= 237 429 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 238 - golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= 239 - golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= 430 + golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 431 + golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 432 + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 433 + golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= 434 + golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= 240 435 google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= 241 436 google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= 242 437 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 438 + gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 243 439 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 244 440 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 441 + gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 442 + gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 443 + gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 444 + gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 445 + gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 245 446 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 447 + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 246 448 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 247 449 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 248 - lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= 249 - lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= 450 + honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 451 + lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg= 452 + lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=
-5
main.go cmd/appview/main.go
··· 510 510 return 511 511 } 512 512 513 - if err != nil { 514 - c.JSON(http.StatusBadGateway, gin.H{"error": fmt.Sprintf("Failed to resolve DID: %v", err)}) 515 - return 516 - } 517 - 518 513 var targetEndpoint string 519 514 for _, svc := range didDoc.Service { 520 515 if svc.Type == "BskyFeedGenerator" && strings.HasSuffix(svc.ID, "#bsky_fg") {
+45 -1
readme.md
··· 15 15 - `app.bsky.feed.getPosts` (post rendering is incomplete) 16 16 - `app.bsky.feed.getFeed` (post rendering is incomplete) 17 17 - `app.bsky.unspecced.getConfig` (placeholder) 18 - - `app.bsky.unspecced.getPostThreadV2` (mostly working! doesnt use prefered sort, not performant) 18 + - `app.bsky.unspecced.getPostThreadV2` (mostly working! doesnt use prefered sort, not performant yet) 19 + 20 + > [!NOTE] 21 + > uh im not very confident with the current directory structure, so files and folders might move around 22 + 23 + ## Runnables 24 + run all of these using `go run .` inside the respective directories 25 + 26 + ### `/cmd/appview` 27 + the main entry point, the actual appview itself. the api server that implements app.bsky.* XRPC methods 28 + 29 + ### `/cmd/backstream` 30 + experimental backfiller that kinda (but not really) conforms to the jetstream event shape. designed to be ingested by consumers expecting jetstream 31 + 32 + ## Packages 33 + 34 + ### `/auth` 35 + taken from [go-bsky-feed-generator](https://github.com/jazware/go-bsky-feed-generator) but modified a bit. 36 + 37 + handles all of the auth, modified to have a more lenient version to make `getFeed` work 38 + 39 + ### `/microcosm/*` 40 + microcosm api clients, implements constellation slingshot and spacedust 41 + 42 + slingshot's api client is compatible with `github.com/bluesky-social/indigo/*` stuff, like `agnostic.RepoGetRecord` and `util.LexClient` 43 + 44 + ### `/shims/*` 45 + most of Red Dwarf Server logic lives here. pulls data from upstream services like microcosm constellation and slingshot, transforms it, and spits out bsky api -like responses using the published app.bsky.* codegen from `github.com/bluesky-social/indigo/api/bsky` 46 + 47 + 48 + ### `/sticket` 49 + unused leftover sorry 50 + 51 + 52 + ### `/store` 53 + unused leftover sorry 54 + 55 + ## todo 56 + 57 + - appview-side query caches 58 + - notification service 59 + - bookmarks service 60 + - create aturilist service 61 + - make backstream usable 62 + - create jetrelay service
utils.go cmd/appview/utils.go