Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ jobs:
echo "::error::Invalid release tag format: $GITHUB_REF_NAME"
exit 1
fi
sed -i '' 's/Version = \"dev\"/Version = \"'"$GITHUB_REF_NAME"'\"/' server/server.go
sed -i '' 's/Version = \"dev\"/Version = \"'"$GITHUB_REF_NAME"'\"/' utils/version.go

- name: Build
run: |
Expand Down Expand Up @@ -250,7 +250,7 @@ jobs:
echo "::error::Invalid release tag format: $GITHUB_REF_NAME"
exit 1
fi
sed -i 's/Version = \"dev\"/Version = \"'"$GITHUB_REF_NAME"'\"/' server/server.go
sed -i 's/Version = \"dev\"/Version = \"'"$GITHUB_REF_NAME"'\"/' utils/version.go

- name: Build
run: |
Expand Down Expand Up @@ -304,7 +304,7 @@ jobs:
Write-Error "Invalid release tag format: $env:GITHUB_REF_NAME"
exit 1
}
sed -i ('s/Version = \"dev\"/Version = \"' + $env:GITHUB_REF_NAME + '\"/') server/server.go
sed -i ('s/Version = \"dev\"/Version = \"' + $env:GITHUB_REF_NAME + '\"/') utils/version.go

- name: Build
run: |
Expand Down
19 changes: 17 additions & 2 deletions cli/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
Expand All @@ -11,6 +12,8 @@

"github.com/spf13/cobra"
"github.com/zalando/go-keyring"

"github.com/mobile-next/mobilecli/utils"
)

const (
Expand Down Expand Up @@ -73,13 +76,25 @@
},
}

// postJSON posts a JSON body to the auth server with mobilecli's User-Agent, so
// device-login traffic is attributable to the CLI in the server access logs.
func postJSON(url string, body []byte) (*http.Response, error) {
req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, url, bytes.NewReader(body))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", utils.UserAgent())
return authHTTPClient.Do(req)
}

func requestDeviceCode() (*deviceCodeResponse, error) {
reqBody, _ := json.Marshal(deviceCodeRequest{ClientID: deviceFlowClientID})

Check failure on line 92 in cli/auth.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `json.Marshal` is not checked (errcheck)
resp, err := authHTTPClient.Post(deviceCodeURL, "application/json", bytes.NewReader(reqBody))
resp, err := postJSON(deviceCodeURL, reqBody)
if err != nil {
return nil, fmt.Errorf("failed to request device code: %w", err)
}
defer resp.Body.Close()

Check failure on line 97 in cli/auth.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `resp.Body.Close` is not checked (errcheck)

respBody, err := io.ReadAll(resp.Body)
if err != nil {
Expand Down Expand Up @@ -112,18 +127,18 @@
for time.Now().Before(deadline) {
time.Sleep(pollInterval)

reqBody, _ := json.Marshal(deviceTokenRequest{

Check failure on line 130 in cli/auth.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `json.Marshal` is not checked (errcheck)
ClientID: deviceFlowClientID,
DeviceCode: deviceCode,
GrantType: deviceGrantType,
})
resp, err := authHTTPClient.Post(deviceTokenURL, "application/json", bytes.NewReader(reqBody))
resp, err := postJSON(deviceTokenURL, reqBody)
if err != nil {
return "", fmt.Errorf("failed to poll for token: %w", err)
}

respBody, err := io.ReadAll(resp.Body)
resp.Body.Close()

Check failure on line 141 in cli/auth.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `resp.Body.Close` is not checked (errcheck)
if err != nil {
return "", fmt.Errorf("failed to read response: %w", err)
}
Expand Down
3 changes: 1 addition & 2 deletions cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"log"

"github.com/mobile-next/mobilecli/commands"
"github.com/mobile-next/mobilecli/server"
"github.com/mobile-next/mobilecli/utils"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -208,7 +207,7 @@ COMMON FLAGS:
CompletionOptions: cobra.CompletionOptions{
HiddenDefaultCmd: true,
},
Version: server.Version,
Version: utils.Version,
SilenceUsage: true,
SilenceErrors: true,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
Expand Down
3 changes: 3 additions & 0 deletions rpc/rest.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"net/http"
"net/url"
"time"

"github.com/mobile-next/mobilecli/utils"
)

// RESTTimeout is the deadline for a single REST call to the fleet server.
Expand Down Expand Up @@ -59,6 +61,7 @@ func RESTCall(token, method, path string, body any, result any) error {
return fmt.Errorf("failed to build request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("User-Agent", utils.UserAgent())
if body != nil {
req.Header.Set("Content-Type", "application/json")
}
Expand Down
2 changes: 2 additions & 0 deletions rpc/rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"time"

"github.com/gorilla/websocket"
"github.com/mobile-next/mobilecli/utils"
)

type Request struct {
Expand Down Expand Up @@ -62,6 +63,7 @@ func Dial(token string) (*websocket.Conn, error) {
}
header := http.Header{}
header.Set("Authorization", "Bearer "+token)
header.Set("User-Agent", utils.UserAgent())
conn, _, err := fleetDialer.Dial(u.String(), header)
return conn, err
}
Expand Down
39 changes: 39 additions & 0 deletions rpc/useragent_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package rpc

import (
"net/http"
"net/http/httptest"
"strings"
"testing"

"github.com/mobile-next/mobilecli/utils"
)

func restCallAgainstServerCapturingUserAgent(t *testing.T) string {
t.Helper()

var seen string
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
seen = r.Header.Get("User-Agent")
if _, err := w.Write([]byte(`{}`)); err != nil {
t.Errorf("failed to write response: %v", err)
}
}))
defer server.Close()

// GetAPIBaseURL derives the REST base from the fleet websocket URL.
t.Setenv("MOBILECLI_FLEET_URL", strings.Replace(server.URL, "http://", "ws://", 1))

if err := RESTCall("token", http.MethodGet, "/api/v1/sessions", nil, nil); err != nil {
t.Fatalf("RESTCall failed: %v", err)
}
return seen
}

func TestRESTCallIdentifiesItselfAsMobilecli(t *testing.T) {
got := restCallAgainstServerCapturingUserAgent(t)
want := "mobilecli/" + utils.Version
if got != want {
t.Errorf("expected User-Agent %q, got %q", want, got)
}
}
4 changes: 1 addition & 3 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,6 @@ const (
IdleTimeout = 120 * time.Second
)

var Version = "dev"

var okResponse = map[string]any{"status": "ok"}

// StreamSession represents a screen capture streaming session
Expand Down Expand Up @@ -1416,7 +1414,7 @@ func handleCrashesGet(params json.RawMessage) (any, error) {
func handleServerInfo(params json.RawMessage) (any, error) {
return map[string]string{
"name": "mobilecli",
"version": Version,
"version": utils.Version,
}, nil
}

Expand Down
12 changes: 12 additions & 0 deletions utils/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package utils

// Version is the mobilecli release version. The release workflow rewrites the
// literal below (see .github/workflows/build.yml); local builds stay "dev".
var Version = "dev"

// UserAgent is the User-Agent mobilecli sends on every outbound HTTP and
// WebSocket request to the cloud. Without it Go sends "Go-http-client/2.0",
// which is indistinguishable from every other Go client in the access logs.
func UserAgent() string {
return "mobilecli/" + Version
}
Loading