diff --git a/.github/workflows/release-linux.yaml b/.github/workflows/release-linux.yaml index 387827e..5dda717 100644 --- a/.github/workflows/release-linux.yaml +++ b/.github/workflows/release-linux.yaml @@ -63,7 +63,10 @@ jobs: - name: Build wails app for Linux shell: bash - run: CGO_ENABLED=1 wails build -platform ${{ matrix.platform }} -ldflags "-X main.version=${{ github.event.release.tag_name }}" -o tiny-rdm + run: | + CGO_ENABLED=1 wails build -platform ${{ matrix.platform }} \ + -ldflags "-X main.version=${{ github.event.release.tag_name }} -X main.gaMeasurementID=${{ secrets.GA_MEASUREMENT_ID }} -X main.gaSecretKey=${{ secrets.LINUX_GA_SECRET }}" \ + -o tiny-rdm - name: Setup control template shell: bash diff --git a/.github/workflows/release-macos.yaml b/.github/workflows/release-macos.yaml index 0c0c747..f5639dd 100644 --- a/.github/workflows/release-macos.yaml +++ b/.github/workflows/release-macos.yaml @@ -69,7 +69,9 @@ jobs: - name: Build wails app for macOS shell: bash - run: CGO_ENABLED=1 wails build -platform ${{ matrix.platform }} -ldflags "-X main.version=${{ github.event.release.tag_name }}" + run: | + CGO_ENABLED=1 wails build -platform ${{ matrix.platform }} \ + -ldflags "-X main.version=${{ github.event.release.tag_name }} -X main.gaMeasurementID=${{ secrets.GA_MEASUREMENT_ID }} -X main.gaSecretKey=${{ secrets.MAC_GA_SECRET }}" # - name: Notarise macOS app + create dmg # shell: bash diff --git a/.github/workflows/release-windows.yaml b/.github/workflows/release-windows.yaml index dc2a09d..f49d070 100644 --- a/.github/workflows/release-windows.yaml +++ b/.github/workflows/release-windows.yaml @@ -62,7 +62,10 @@ jobs: - name: Build Windows portable app shell: bash - run: CGO_ENABLED=1 wails build -clean -platform ${{ matrix.platform }} -upx -webview2 embed -ldflags "-X main.version=${{ github.event.release.tag_name }}" + run: | + CGO_ENABLED=1 wails build -clean -platform ${{ matrix.platform }} \ + -upx -webview2 embed \ + -ldflags "-X main.version=${{ github.event.release.tag_name }} -X main.gaMeasurementID=${{ secrets.GA_MEASUREMENT_ID }} -X main.gaSecretKey=${{ secrets.WINDOWS_GA_SECRET }}" - name: Compress portable binary working-directory: ./build/bin diff --git a/backend/services/ga_service.go b/backend/services/ga_service.go new file mode 100644 index 0000000..66b97d9 --- /dev/null +++ b/backend/services/ga_service.go @@ -0,0 +1,110 @@ +package services + +import ( + "bytes" + "encoding/json" + "github.com/google/uuid" + "net/http" + "runtime" + "strings" + "sync" + "tinyrdm/backend/storage" +) + +// google analytics service +type gaService struct { + measurementID string + secretKey string + clientID string +} + +type GaDataItem struct { + ClientID string `json:"client_id"` + Events []GaEventItem `json:"events"` +} + +type GaEventItem struct { + Name string `json:"name"` + Params map[string]any `json:"params"` +} + +var ga *gaService +var onceGA sync.Once + +func GA() *gaService { + if ga == nil { + onceGA.Do(func() { + // get or create an unique user id + st := storage.NewLocalStore("device.txt") + uidByte, err := st.Load() + if err != nil { + uidByte = []byte(strings.ReplaceAll(uuid.NewString(), "-", "")) + st.Store(uidByte) + } + + ga = &gaService{ + clientID: string(uidByte), + } + }) + } + return ga +} + +func (a *gaService) SetSecretKey(measurementID, secretKey string) { + a.measurementID = measurementID + a.secretKey = secretKey +} + +func (a *gaService) isValid() bool { + return len(a.measurementID) > 0 && len(a.secretKey) > 0 +} + +func (a *gaService) sendEvent(events ...GaEventItem) error { + body, err := json.Marshal(GaDataItem{ + ClientID: a.clientID, + Events: events, + }) + if err != nil { + return err + } + + //url := "https://www.google-analytics.com/debug/mp/collect" + url := "https://www.google-analytics.com/mp/collect" + req, err := http.NewRequest("POST", url, bytes.NewBuffer(body)) + if err != nil { + return err + } + + q := req.URL.Query() + q.Add("measurement_id", a.measurementID) + q.Add("api_secret", a.secretKey) + req.URL.RawQuery = q.Encode() + + response, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + defer response.Body.Close() + + //if dump, err := httputil.DumpResponse(response, true); err == nil { + // log.Println(string(dump)) + //} + + return nil +} + +// Startup sends application startup event +func (a *gaService) Startup(version string) { + if !a.isValid() { + return + } + + go a.sendEvent(GaEventItem{ + Name: "startup", + Params: map[string]any{ + "os": runtime.GOOS, + "arch": runtime.GOARCH, + "version": version, + }, + }) +} diff --git a/backend/services/storage_service.go b/backend/services/storage_service.go deleted file mode 100644 index 5d540f1..0000000 --- a/backend/services/storage_service.go +++ /dev/null @@ -1,18 +0,0 @@ -package services - -import "sync" - -type storageService struct { -} - -var storage *storageService -var onceStorage sync.Once - -func Storage() *storageService { - if storage == nil { - onceStorage.Do(func() { - storage = &storageService{} - }) - } - return storage -} diff --git a/go.mod b/go.mod index dce3d82..7da3d7f 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.21 require ( github.com/adrg/sysfont v0.1.2 github.com/andybalholm/brotli v1.0.5 + github.com/google/uuid v1.3.1 github.com/redis/go-redis/v9 v9.2.1 github.com/vrischmann/userdir v0.0.0-20151206171402-20f291cebd68 github.com/wailsapp/wails/v2 v2.6.0 @@ -19,7 +20,6 @@ require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/go-ole/go-ole v1.3.0 // indirect - github.com/google/uuid v1.3.1 // indirect github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect github.com/labstack/echo/v4 v4.11.1 // indirect github.com/labstack/gommon v0.4.0 // indirect diff --git a/main.go b/main.go index ee76a97..decedeb 100644 --- a/main.go +++ b/main.go @@ -22,6 +22,7 @@ var assets embed.FS var icon []byte var version = "0.0.0" +var gaMeasurementID, gaSecretKey string func main() { // Create an instance of the app structure @@ -55,6 +56,9 @@ func main() { OnStartup: func(ctx context.Context) { sysSvc.Start(ctx) connSvc.Start(ctx) + + services.GA().SetSecretKey(gaMeasurementID, gaSecretKey) + services.GA().Startup(version) }, OnShutdown: func(ctx context.Context) { connSvc.Stop(ctx)