go scratch code for atproto
1package didplc
2
3import (
4 "bytes"
5 "context"
6 "encoding/json"
7 "errors"
8 "fmt"
9 "io"
10 "net/http"
11 "strings"
12
13 "github.com/bluesky-social/indigo/atproto/crypto"
14)
15
16// the zero-value of this client is fully functional
17type Client struct {
18 DirectoryURL string
19 UserAgent *string
20 HTTPClient http.Client
21 RotationKey *crypto.PrivateKey
22}
23
24var (
25 ErrDIDNotFound = errors.New("DID not found in PLC directory")
26 DefaultDirectoryURL = "https://plc.directory"
27)
28
29func (c *Client) Resolve(ctx context.Context, did string) (*Doc, error) {
30 if !strings.HasPrefix(did, "did:plc:") {
31 return nil, fmt.Errorf("expected a did:plc, got: %s", did)
32 }
33
34 plcURL := c.DirectoryURL
35 if plcURL == "" {
36 plcURL = DefaultDirectoryURL
37 }
38
39 url := plcURL + "/" + did
40 req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
41 if err != nil {
42 return nil, err
43 }
44 if c.UserAgent != nil {
45 req.Header.Set("User-Agent", *c.UserAgent)
46 } else {
47 req.Header.Set("User-Agent", "go-did-method-plc")
48 }
49
50 resp, err := c.HTTPClient.Do(req)
51 if err != nil {
52 return nil, fmt.Errorf("failed did:plc directory resolution: %w", err)
53 }
54 if resp.StatusCode == http.StatusNotFound {
55 return nil, ErrDIDNotFound
56 }
57 if resp.StatusCode != http.StatusOK {
58 return nil, fmt.Errorf("failed did:web well-known fetch, HTTP status: %d", resp.StatusCode)
59 }
60
61 var doc Doc
62 if err := json.NewDecoder(resp.Body).Decode(&doc); err != nil {
63 return nil, fmt.Errorf("failed parse of did:plc document JSON: %w", err)
64 }
65 return &doc, nil
66}
67
68func (c *Client) Submit(ctx context.Context, did string, op Operation) (*LogEntry, error) {
69 if !strings.HasPrefix(did, "did:plc:") {
70 return nil, fmt.Errorf("expected a did:plc, got: %s", did)
71 }
72
73 plcURL := c.DirectoryURL
74 if plcURL == "" {
75 plcURL = DefaultDirectoryURL
76 }
77
78 var body io.Reader
79 b, err := json.Marshal(op)
80 if err != nil {
81 return nil, err
82 }
83 body = bytes.NewReader(b)
84
85 url := plcURL + "/" + did
86 req, err := http.NewRequestWithContext(ctx, "POST", url, body)
87 if err != nil {
88 return nil, err
89 }
90 req.Header.Set("Content-Type", "application/json")
91 if c.UserAgent != nil {
92 req.Header.Set("User-Agent", *c.UserAgent)
93 } else {
94 req.Header.Set("User-Agent", "go-did-method-plc")
95 }
96
97 resp, err := c.HTTPClient.Do(req)
98 if err != nil {
99 return nil, fmt.Errorf("did:plc operation submission failed: %w", err)
100 }
101 if resp.StatusCode == http.StatusNotFound {
102 return nil, ErrDIDNotFound
103 }
104 if resp.StatusCode != http.StatusOK {
105 return nil, fmt.Errorf("failed did:plc operation submission, HTTP status: %d", resp.StatusCode)
106 }
107
108 var entry LogEntry
109 if err := json.NewDecoder(resp.Body).Decode(&entry); err != nil {
110 return nil, fmt.Errorf("failed parse of did:plc op log entry: %w", err)
111 }
112 return &entry, nil
113}
114
115func (c *Client) OpLog(ctx context.Context, did string, audit bool) ([]LogEntry, error) {
116 if !strings.HasPrefix(did, "did:plc:") {
117 return nil, fmt.Errorf("expected a did:plc, got: %s", did)
118 }
119
120 plcURL := c.DirectoryURL
121 if plcURL == "" {
122 plcURL = DefaultDirectoryURL
123 }
124
125 url := plcURL + "/" + did + "/log"
126 if audit {
127 url += "/audit"
128 }
129 req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
130 if err != nil {
131 return nil, err
132 }
133 if c.UserAgent != nil {
134 req.Header.Set("User-Agent", *c.UserAgent)
135 } else {
136 req.Header.Set("User-Agent", "go-did-method-plc")
137 }
138
139 resp, err := c.HTTPClient.Do(req)
140 if err != nil {
141 return nil, fmt.Errorf("failed did:plc directory resolution: %w", err)
142 }
143 if resp.StatusCode == http.StatusNotFound {
144 return nil, ErrDIDNotFound
145 }
146 if resp.StatusCode != http.StatusOK {
147 return nil, fmt.Errorf("failed did:web well-known fetch, HTTP status: %d", resp.StatusCode)
148 }
149
150 var entries []LogEntry
151 if err := json.NewDecoder(resp.Body).Decode(&entries); err != nil {
152 return nil, fmt.Errorf("failed parse of did:plc document JSON: %w", err)
153 }
154 return entries, nil
155}