diff --git a/.goreleaser.yaml b/.goreleaser.yaml index b1c08ab8d..137c4ddb9 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -55,4 +55,4 @@ release: owner: onflow name: flow-cli prerelease: auto - draft: false \ No newline at end of file + draft: false diff --git a/go.mod b/go.mod index 77a9976dc..0d2c7726d 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/onflow/fcl-dev-wallet v0.8.0 github.com/onflow/flixkit-go/v2 v2.3.0 github.com/onflow/flow-core-contracts/lib/go/templates v1.6.1 - github.com/onflow/flow-emulator v1.3.0 + github.com/onflow/flow-emulator v1.4.0 github.com/onflow/flow-evm-gateway v1.0.3 github.com/onflow/flow-go v0.38.1-0.20250218174738-2181389f9f7d github.com/onflow/flow-go-sdk v1.4.0 diff --git a/go.sum b/go.sum index 784da6945..1289907dd 100644 --- a/go.sum +++ b/go.sum @@ -844,8 +844,8 @@ github.com/onflow/flow-core-contracts/lib/go/contracts v1.5.1-preview h1:W+QkNQc github.com/onflow/flow-core-contracts/lib/go/contracts v1.5.1-preview/go.mod h1:LyCICUK6sK1jtEyb+3GuRw5tYfHT1uxACLwLTLxw/0I= github.com/onflow/flow-core-contracts/lib/go/templates v1.6.1 h1:Y0bDvS5fTOCrKr7QFl0by3qTq7MFnauVnHoxwW6nQzo= github.com/onflow/flow-core-contracts/lib/go/templates v1.6.1/go.mod h1:pN768Al/wLRlf3bwugv9TyxniqJxMu4sxnX9eQJam64= -github.com/onflow/flow-emulator v1.3.0 h1:SqKbXZ79GcfnDDaggQccEkp6fBMFQVG4AxRtpT+jYYk= -github.com/onflow/flow-emulator v1.3.0/go.mod h1:+RV5j5TYEE0VYtUAEn/vduecy8yK8P+rB+a9BCc3gQA= +github.com/onflow/flow-emulator v1.4.0 h1:LSNYV4Dd6Aetd2Q/4qc1mes5BIt4JTKMoQfr5QXdBFQ= +github.com/onflow/flow-emulator v1.4.0/go.mod h1:+RV5j5TYEE0VYtUAEn/vduecy8yK8P+rB+a9BCc3gQA= github.com/onflow/flow-evm-gateway v1.0.3 h1:Mlz3EBVTz7wpUwrDTVSJtd8CG/DtEe920UDUPiTGN7Y= github.com/onflow/flow-evm-gateway v1.0.3/go.mod h1:JukrDrisLQvup7FwCkA/z7LEKtLpCETPSu25CAuuEN0= github.com/onflow/flow-ft/lib/go/contracts v1.0.1 h1:Ts5ob+CoCY2EjEd0W6vdLJ7hLL3SsEftzXG2JlmSe24= diff --git a/internal/command/command.go b/internal/command/command.go index c6fe890c2..232766fc7 100644 --- a/internal/command/command.go +++ b/internal/command/command.go @@ -364,14 +364,14 @@ func initCrashReporting() { } // The token is injected at build-time using ldflags -var mixpanelToken = "" +var MixpanelToken = "" func UsageMetrics(command *cobra.Command, wg *sync.WaitGroup) { - if !settings.MetricsEnabled() || mixpanelToken == "" { + if !settings.MetricsEnabled() || MixpanelToken == "" { return } wg.Add(1) - client := mixpanel.New(mixpanelToken, "") + client := mixpanel.New(MixpanelToken, "") // calculates a user ID that doesn't leak any personal information usr, _ := user.Current() // ignore err, just use empty string diff --git a/internal/emulator/start.go b/internal/emulator/start.go index 1bbd8620d..c7fe7b621 100644 --- a/internal/emulator/start.go +++ b/internal/emulator/start.go @@ -19,11 +19,17 @@ package emulator import ( + "crypto/sha256" + "encoding/base64" "errors" "fmt" + "net/http" "os" + "os/user" + "runtime" "sync" + "github.com/dukex/mixpanel" "github.com/onflow/flow-emulator/cmd/emulator/start" "github.com/onflow/flow-go-sdk/crypto" "github.com/spf13/afero" @@ -32,12 +38,17 @@ import ( "github.com/onflow/flowkit/v2" "github.com/onflow/flowkit/v2/config" + "github.com/onflow/flow-cli/build" "github.com/onflow/flow-cli/internal/command" + "github.com/onflow/flow-cli/internal/settings" "github.com/onflow/flow-cli/internal/util" ) var Cmd *cobra.Command +// Mixpanel client to be reused on each http request of the middleware +var mixpanelClient mixpanel.Mixpanel + func configuredServiceKey( init bool, _ crypto.SignatureAlgorithm, @@ -97,8 +108,45 @@ func configuredServiceKey( return *privateKey, serviceAccount.Key.SigAlgo(), serviceAccount.Key.HashAlgo() } +func trackRequestMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Generate a unique user ID + usr, _ := user.Current() // ignore err, just use empty string + hash := sha256.Sum256([]byte(fmt.Sprintf("%s%s", usr.Username, usr.Uid))) + userID := base64.StdEncoding.EncodeToString(hash[:]) + + // Track the request in Mixpanel + _ = mixpanelClient.Track(userID, "emulator-request", &mixpanel.Event{ + IP: "0", // do not track IPs + Properties: map[string]any{ + "method": r.Method, + "url": r.URL.String(), + "version": build.Semver(), + "os": runtime.GOOS, + "ci": os.Getenv("CI") != "", // CI is commonly set by CI providers + }, + }) + + // Call the next handler + next.ServeHTTP(w, r) + }) +} + func init() { - Cmd = start.Cmd(configuredServiceKey) + // Initialize mixpanel client only if metrics are enabled and token is not empty + if settings.MetricsEnabled() && command.MixpanelToken != "" { + mixpanelClient = mixpanel.New(command.MixpanelToken, "") + Cmd = start.Cmd(start.StartConfig{ + GetServiceKey: configuredServiceKey, + RestMiddlewares: []start.HttpMiddleware{trackRequestMiddleware}, + }) + } else { + Cmd = start.Cmd(start.StartConfig{ + GetServiceKey: configuredServiceKey, + RestMiddlewares: []start.HttpMiddleware{}, + }) + } + Cmd.Use = "emulator" Cmd.Short = "Run Flow network for development" Cmd.GroupID = "tools"