diff --git a/auth/auth.go b/auth/auth.go index 0bda04a0..a7028698 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -21,7 +21,6 @@ import ( "errors" "fmt" "os" - "runtime" "strings" "time" @@ -134,12 +133,11 @@ func NewClient(ctx context.Context, conf *internal.AuthConfig) (*Client, error) return nil, err } - goVersion := strings.TrimPrefix(runtime.Version(), "go") hc := internal.WithDefaultRetryConfig(transport) hc.CreateErrFn = handleHTTPError hc.Opts = []internal.HTTPOption{ internal.WithHeader("X-Client-Version", fmt.Sprintf("Go/Admin/%s", conf.Version)), - internal.WithHeader("x-goog-api-client", fmt.Sprintf("gl-go/%s fire-admin/%s", goVersion, conf.Version)), + internal.WithHeader("x-goog-api-client", internal.GetMetricsHeader(conf.Version)), } baseURL := defaultAuthURL diff --git a/auth/auth_test.go b/auth/auth_test.go index 2a00220b..9c8b1523 100644 --- a/auth/auth_test.go +++ b/auth/auth_test.go @@ -23,7 +23,6 @@ import ( "log" "net/http" "os" - "runtime" "strings" "syscall" "testing" @@ -1452,11 +1451,9 @@ func checkBaseClient(client *Client, wantProjectID string) error { return fmt.Errorf("version = %q; want = %q", version, wantVersion) } - goVersion := strings.TrimPrefix(runtime.Version(), "go") - xGoogAPIClientHeader := req.Header.Get("x-goog-api-client") - wantXGoogAPIClientHeader := fmt.Sprintf("gl-go/%s fire-admin/%s", goVersion, testVersion) - if xGoogAPIClientHeader != wantXGoogAPIClientHeader { - return fmt.Errorf("x-goog-api-client header = %q; want = %q", xGoogAPIClientHeader, wantXGoogAPIClientHeader) + xGoogAPIClientHeader := internal.GetMetricsHeader(testVersion) + if h := req.Header.Get("x-goog-api-client"); h != xGoogAPIClientHeader { + return fmt.Errorf("x-goog-api-client header = %q; want = %q", h, xGoogAPIClientHeader) } return nil diff --git a/auth/token_generator.go b/auth/token_generator.go index 0e18b8c8..7aa2d564 100644 --- a/auth/token_generator.go +++ b/auth/token_generator.go @@ -175,6 +175,9 @@ func newIAMSigner(ctx context.Context, config *internal.AuthConfig) (*iamSigner, if err != nil { return nil, err } + hc.Opts = []internal.HTTPOption{ + internal.WithHeader("x-goog-api-client", internal.GetMetricsHeader(config.Version)), + } return &iamSigner{ mutex: &sync.Mutex{}, diff --git a/auth/token_generator_test.go b/auth/token_generator_test.go index c80c4dea..c79e3a9a 100644 --- a/auth/token_generator_test.go +++ b/auth/token_generator_test.go @@ -122,6 +122,7 @@ func TestIAMSigner(t *testing.T) { conf := &internal.AuthConfig{ Opts: optsWithTokenSource, ServiceAccountID: "test-service-account", + Version: testVersion, } signer, err := newIAMSigner(ctx, conf) if err != nil { @@ -155,6 +156,7 @@ func TestIAMSignerHTTPError(t *testing.T) { conf := &internal.AuthConfig{ Opts: optsWithTokenSource, ServiceAccountID: "test-service-account", + Version: testVersion, } signer, err := newIAMSigner(context.Background(), conf) if err != nil { @@ -182,6 +184,7 @@ func TestIAMSignerUnknownHTTPError(t *testing.T) { conf := &internal.AuthConfig{ Opts: optsWithTokenSource, ServiceAccountID: "test-service-account", + Version: testVersion, } signer, err := newIAMSigner(context.Background(), conf) if err != nil { @@ -208,7 +211,8 @@ func TestIAMSignerUnknownHTTPError(t *testing.T) { func TestIAMSignerWithMetadataService(t *testing.T) { ctx := context.Background() conf := &internal.AuthConfig{ - Opts: optsWithTokenSource, + Opts: optsWithTokenSource, + Version: testVersion, } signer, err := newIAMSigner(ctx, conf) @@ -253,7 +257,8 @@ func TestIAMSignerWithMetadataService(t *testing.T) { func TestIAMSignerNoMetadataService(t *testing.T) { ctx := context.Background() conf := &internal.AuthConfig{ - Opts: optsWithTokenSource, + Opts: optsWithTokenSource, + Version: testVersion, } signer, err := newIAMSigner(ctx, conf) @@ -340,6 +345,10 @@ func iamServer(t *testing.T, serviceAcct, signature string) *httptest.Server { if r.URL.Path != wantPath { t.Errorf("Path = %q; want = %q", r.URL.Path, wantPath) } + xGoogAPIClientHeader := internal.GetMetricsHeader(testVersion) + if h := r.Header.Get("x-goog-api-client"); h != xGoogAPIClientHeader { + t.Errorf("x-goog-api-client header = %q; want = %q", h, xGoogAPIClientHeader) + } w.Header().Set("Content-Type", "application/json") b, err := json.Marshal(resp) diff --git a/auth/user_mgt_test.go b/auth/user_mgt_test.go index 902ed280..97e64c39 100644 --- a/auth/user_mgt_test.go +++ b/auth/user_mgt_test.go @@ -24,7 +24,6 @@ import ( "net/http" "net/http/httptest" "reflect" - "runtime" "sort" "strconv" "strings" @@ -2317,7 +2316,7 @@ func echoServer(resp interface{}, t *testing.T) *mockAuthServer { } gh = r.Header.Get("x-goog-api-client") - wh = fmt.Sprintf("gl-go/%s fire-admin/%s", strings.TrimPrefix(runtime.Version(), "go"), testVersion) + wh = internal.GetMetricsHeader(testVersion) if gh != wh { t.Errorf("x-goog-api-client header = %q; want: %q", gh, wh) } diff --git a/firebase.go b/firebase.go index 8b1facd4..98e04a07 100644 --- a/firebase.go +++ b/firebase.go @@ -115,6 +115,7 @@ func (a *App) InstanceID(ctx context.Context) (*iid.Client, error) { conf := &internal.InstanceIDConfig{ ProjectID: a.projectID, Opts: a.opts, + Version: Version, } return iid.NewClient(ctx, conf) } diff --git a/iid/iid.go b/iid/iid.go index 96486a6a..7a3e9b55 100644 --- a/iid/iid.go +++ b/iid/iid.go @@ -119,6 +119,9 @@ func NewClient(ctx context.Context, c *internal.InstanceIDConfig) (*Client, erro if err != nil { return nil, err } + hc.Opts = []internal.HTTPOption{ + internal.WithHeader("x-goog-api-client", internal.GetMetricsHeader(c.Version)), + } hc.CreateErrFn = createError return &Client{ diff --git a/iid/iid_test.go b/iid/iid_test.go index c9134207..208da357 100644 --- a/iid/iid_test.go +++ b/iid/iid_test.go @@ -31,6 +31,7 @@ var testIIDConfig = &internal.InstanceIDConfig{ Opts: []option.ClientOption{ option.WithTokenSource(&internal.MockTokenSource{AccessToken: "test-token"}), }, + Version: "test-version", } func TestNoProjectID(t *testing.T) { @@ -83,6 +84,10 @@ func TestDeleteInstanceID(t *testing.T) { if h := tr.Header.Get("Authorization"); h != "Bearer test-token" { t.Errorf("Authorization = %q; want = %q", h, "Bearer test-token") } + xGoogAPIClientHeader := internal.GetMetricsHeader(testIIDConfig.Version) + if h := tr.Header.Get("x-goog-api-client"); h != xGoogAPIClientHeader { + t.Errorf("x-goog-api-client header = %q; want = %q", h, xGoogAPIClientHeader) + } } func TestDeleteInstanceIDError(t *testing.T) { @@ -156,6 +161,10 @@ func TestDeleteInstanceIDError(t *testing.T) { if h := tr.Header.Get("Authorization"); h != "Bearer test-token" { t.Errorf("Authorization = %q; want = %q", h, "Bearer test-token") } + xGoogAPIClientHeader := internal.GetMetricsHeader(testIIDConfig.Version) + if h := tr.Header.Get("x-goog-api-client"); h != xGoogAPIClientHeader { + t.Errorf("x-goog-api-client header = %q; want = %q", h, xGoogAPIClientHeader) + } tr = nil } } @@ -201,6 +210,10 @@ func TestDeleteInstanceIDUnexpectedError(t *testing.T) { if h := tr.Header.Get("Authorization"); h != "Bearer test-token" { t.Errorf("Authorization = %q; want = %q", h, "Bearer test-token") } + xGoogAPIClientHeader := internal.GetMetricsHeader(testIIDConfig.Version) + if h := tr.Header.Get("x-goog-api-client"); h != xGoogAPIClientHeader { + t.Errorf("x-goog-api-client header = %q; want = %q", h, xGoogAPIClientHeader) + } } func TestDeleteInstanceIDConnectionError(t *testing.T) { diff --git a/internal/http_client.go b/internal/http_client.go index 280aecc4..9463f98e 100644 --- a/internal/http_client.go +++ b/internal/http_client.go @@ -23,7 +23,9 @@ import ( "io/ioutil" "math" "net/http" + "runtime" "strconv" + "strings" "time" "google.golang.org/api/option" @@ -441,3 +443,9 @@ func retryNetworkAndHTTPErrors(statusCodes ...int) RetryCondition { return false } } + +// GetMetricsHeader constructs header value for metrics attribution +func GetMetricsHeader(sdkVersion string) string { + goVersion := strings.TrimPrefix(runtime.Version(), "go") + return fmt.Sprintf("gl-go/%s fire-admin/%s", goVersion, sdkVersion) +} diff --git a/internal/internal.go b/internal/internal.go index 287391c5..9fb9e726 100644 --- a/internal/internal.go +++ b/internal/internal.go @@ -50,6 +50,7 @@ type HashConfig map[string]interface{} type InstanceIDConfig struct { Opts []option.ClientOption ProjectID string + Version string } // DatabaseConfig represents the configuration of Firebase Database service. diff --git a/messaging/messaging.go b/messaging/messaging.go index 525cccb6..dcd412c3 100644 --- a/messaging/messaging.go +++ b/messaging/messaging.go @@ -23,7 +23,6 @@ import ( "fmt" "net/http" "regexp" - "runtime" "strconv" "strings" "time" @@ -878,7 +877,7 @@ func NewClient(ctx context.Context, c *internal.MessagingConfig) (*Client, error return &Client{ fcmClient: newFCMClient(hc, c, messagingEndpoint, batchEndpoint), - iidClient: newIIDClient(hc), + iidClient: newIIDClient(hc, c), }, nil } @@ -894,12 +893,11 @@ func newFCMClient(hc *http.Client, conf *internal.MessagingConfig, messagingEndp client := internal.WithDefaultRetryConfig(hc) client.CreateErrFn = handleFCMError - goVersion := strings.TrimPrefix(runtime.Version(), "go") version := fmt.Sprintf("fire-admin-go/%s", conf.Version) client.Opts = []internal.HTTPOption{ internal.WithHeader(apiFormatVersionHeader, apiFormatVersion), internal.WithHeader(firebaseClientHeader, version), - internal.WithHeader("x-goog-api-client", fmt.Sprintf("gl-go/%s fire-admin/%s", goVersion, conf.Version)), + internal.WithHeader("x-goog-api-client", internal.GetMetricsHeader(conf.Version)), } return &fcmClient{ diff --git a/messaging/messaging_test.go b/messaging/messaging_test.go index 8b31fc46..dceed542 100644 --- a/messaging/messaging_test.go +++ b/messaging/messaging_test.go @@ -21,8 +21,6 @@ import ( "net/http" "net/http/httptest" "reflect" - "runtime" - "strings" "testing" "time" @@ -1396,8 +1394,7 @@ func checkFCMRequest(t *testing.T, b []byte, tr *http.Request, want map[string]i if h := tr.Header.Get("X-FIREBASE-CLIENT"); h != clientVersion { t.Errorf("X-FIREBASE-CLIENT = %q; want = %q", h, clientVersion) } - goVersion := strings.TrimPrefix(runtime.Version(), "go") - xGoogAPIClientHeader := "gl-go/" + goVersion + " fire-admin/" + testMessagingConfig.Version + xGoogAPIClientHeader := internal.GetMetricsHeader(testMessagingConfig.Version) if h := tr.Header.Get("x-goog-api-client"); h != xGoogAPIClientHeader { t.Errorf("x-goog-api-client header = %q; want = %q", h, xGoogAPIClientHeader) } diff --git a/messaging/topic_mgt.go b/messaging/topic_mgt.go index 15a29787..88492585 100644 --- a/messaging/topic_mgt.go +++ b/messaging/topic_mgt.go @@ -63,10 +63,13 @@ type iidClient struct { httpClient *internal.HTTPClient } -func newIIDClient(hc *http.Client) *iidClient { +func newIIDClient(hc *http.Client, conf *internal.MessagingConfig) *iidClient { client := internal.WithDefaultRetryConfig(hc) client.CreateErrFn = handleIIDError - client.Opts = []internal.HTTPOption{internal.WithHeader("access_token_auth", "true")} + client.Opts = []internal.HTTPOption{ + internal.WithHeader("access_token_auth", "true"), + internal.WithHeader("x-goog-api-client", internal.GetMetricsHeader(conf.Version)), + } return &iidClient{ iidEndpoint: iidEndpoint, httpClient: client, diff --git a/messaging/topic_mgt_test.go b/messaging/topic_mgt_test.go index 15ea45d1..6d58114d 100644 --- a/messaging/topic_mgt_test.go +++ b/messaging/topic_mgt_test.go @@ -25,6 +25,7 @@ import ( "testing" "firebase.google.com/go/v4/errorutils" + "firebase.google.com/go/v4/internal" ) func TestSubscribe(t *testing.T) { @@ -198,6 +199,10 @@ func checkIIDRequest(t *testing.T, b []byte, tr *http.Request, op string) { if h := tr.Header.Get("Authorization"); h != "Bearer test-token" { t.Errorf("Authorization = %q; want = %q", h, "Bearer test-token") } + xGoogAPIClientHeader := internal.GetMetricsHeader(testMessagingConfig.Version) + if h := tr.Header.Get("x-goog-api-client"); h != xGoogAPIClientHeader { + t.Errorf("x-goog-api-client header = %q; want = %q", h, xGoogAPIClientHeader) + } } func checkTopicMgtResponse(t *testing.T, resp *TopicManagementResponse) {