bluesky appview implementation using microcosm and other services server.reddwarf.app
appview bluesky reddwarf microcosm

backstream init

Changed files
+1354 -24
backstream
cmd
+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