From c827ea7a2575f103b7034b4c7719e488894aae4b Mon Sep 17 00:00:00 2001 From: Alex Kuznicki Date: Fri, 27 Feb 2026 08:46:30 -0700 Subject: [PATCH 1/2] Add User-Agent header derived from Go BuildInfo --- go/auth.go | 5 +++++ go/client.go | 4 ++-- go/client_test.go | 32 ++++++++++++++++++++++++++++++++ go/stream.go | 2 +- go/version.go | 29 +++++++++++++++++++++++++++++ 5 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 go/version.go diff --git a/go/auth.go b/go/auth.go index 665179d..b439e9e 100644 --- a/go/auth.go +++ b/go/auth.go @@ -30,3 +30,8 @@ func generateAuthHeaders(h http.Header, method string, path string, body []byte, h.Add(authzTSHeader, strconv.FormatInt(timestamp, 10)) h.Add(authzSigHeader, hmacString) } + +func setRequestHeaders(h http.Header, method string, path string, body []byte, clientId string, userSecret string, timestamp int64) { + h.Set("User-Agent", userAgent()) + generateAuthHeaders(h, method, path, body, clientId, userSecret, timestamp) +} diff --git a/go/client.go b/go/client.go index 8e93daa..118f649 100644 --- a/go/client.go +++ b/go/client.go @@ -285,7 +285,7 @@ func (c *client) rest(ctx context.Context, d *request, dst interface{}) (err err return err } - generateAuthHeaders(req.Header, req.Method, reqURL.RequestURI(), d.body, + setRequestHeaders(req.Header, req.Method, reqURL.RequestURI(), d.body, c.config.ApiKey, c.config.ApiSecret, time.Now().UnixMilli()) if value := ctx.Value(CustomHeadersCtxKey); value != nil { @@ -357,7 +357,7 @@ func (c *client) serverHeaders(ctx context.Context, u *url.URL) (h http.Header, return nil, err } - generateAuthHeaders(req.Header, req.Method, reqURL.RequestURI(), nil, + setRequestHeaders(req.Header, req.Method, reqURL.RequestURI(), nil, c.config.ApiKey, c.config.ApiSecret, time.Now().UnixMilli()) c.config.logDebug( diff --git a/go/client_test.go b/go/client_test.go index d5253c1..420d285 100644 --- a/go/client_test.go +++ b/go/client_test.go @@ -8,6 +8,7 @@ import ( "net/http/httptest" "reflect" "strconv" + "strings" "testing" "github.com/ethereum/go-ethereum/common/hexutil" @@ -466,6 +467,37 @@ func Test_extractOrigins(t *testing.T) { } } +func TestUserAgent(t *testing.T) { + ua := userAgent() + t.Logf("User-Agent: %s", ua) + if ua == "" { + t.Fatal("userAgent() returned empty string") + } + if !strings.HasPrefix(ua, "data-streams-sdk-go/") { + t.Errorf("userAgent() = %q, want prefix %q", ua, "data-streams-sdk-go/") + } +} + +func TestUserAgentSentInRequests(t *testing.T) { + ms := newMockServer(func(w http.ResponseWriter, r *http.Request) { + got := r.Header.Get("User-Agent") + if !strings.HasPrefix(got, "data-streams-sdk-go/") { + t.Errorf("User-Agent header = %q, want prefix %q", got, "data-streams-sdk-go/") + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(feedsResponse{Feeds: []*feed.Feed{}}) //nolint:errcheck + }) + defer ms.Close() + + client, err := ms.Client() + if err != nil { + t.Fatalf("error creating client: %s", err) + } + + _, _ = client.GetFeeds(context.Background()) +} + + type mockServer struct { server *httptest.Server } diff --git a/go/stream.go b/go/stream.go index acd1ec5..6f0661d 100644 --- a/go/stream.go +++ b/go/stream.go @@ -424,7 +424,7 @@ func (s *stream) newWSconn(ctx context.Context, origin string) (ws *wsConn, err reqURL.RawQuery = url.Values{"feedIDs": {strings.Join(feedIdsToStringList(s.feedIDs), ",")}}.Encode() headers := http.Header{} - generateAuthHeaders(headers, http.MethodGet, reqURL.RequestURI(), nil, + setRequestHeaders(headers, http.MethodGet, reqURL.RequestURI(), nil, s.config.ApiKey, s.config.ApiSecret, time.Now().UnixMilli()) if origin != "" { diff --git a/go/version.go b/go/version.go new file mode 100644 index 0000000..11508dc --- /dev/null +++ b/go/version.go @@ -0,0 +1,29 @@ +package streams + +import ( + "runtime/debug" + "strings" + "sync" +) + +const modulePath = "github.com/smartcontractkit/data-streams-sdk/go" + +func matchesModule(path string) bool { + return path == modulePath || strings.HasPrefix(path, modulePath+"/v") +} + +// userAgent returns this Go SDK version and is appended to the User-Agent header for all requests. +// this is wrapped in sync.OnceValue to avoid repeated expensive calls to debug.ReadBuildInfo. +var userAgent = sync.OnceValue(func() string { + version := "unknown" + bi, ok := debug.ReadBuildInfo() + if ok { + for _, dep := range bi.Deps { + if matchesModule(dep.Path) { + version = dep.Version + break + } + } + } + return "data-streams-sdk-go/" + version +}) From f5e51188a3a3fee7e1f56f0030e5c98a7785fed6 Mon Sep 17 00:00:00 2001 From: Alex Kuznicki Date: Fri, 27 Feb 2026 08:55:21 -0700 Subject: [PATCH 2/2] add test assertion --- go/client_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/go/client_test.go b/go/client_test.go index 420d285..958dd5b 100644 --- a/go/client_test.go +++ b/go/client_test.go @@ -494,10 +494,12 @@ func TestUserAgentSentInRequests(t *testing.T) { t.Fatalf("error creating client: %s", err) } - _, _ = client.GetFeeds(context.Background()) + _, err = client.GetFeeds(context.Background()) + if err != nil { + t.Fatalf("GetFeeds() error = %v", err) + } } - type mockServer struct { server *httptest.Server }