From 61bb36edf958d7554131321552e2d8011d824320 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Fri, 18 Oct 2024 11:15:08 -0600 Subject: [PATCH 1/8] Update to the latest flashlight with bandit dialer and fronted logging fixes --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 827659334..e3da13173 100644 --- a/go.mod +++ b/go.mod @@ -38,8 +38,8 @@ require ( github.com/getlantern/eventual v1.0.0 github.com/getlantern/eventual/v2 v2.0.2 github.com/getlantern/filepersist v0.0.0-20210901195658-ed29a1cb0b7c - github.com/getlantern/flashlight/v7 v7.6.116 - github.com/getlantern/fronted v0.0.0-20240822220559-6e97652d23cc + github.com/getlantern/flashlight/v7 v7.6.118 + github.com/getlantern/fronted v0.0.0-20241018162326-8501716d3eb0 github.com/getlantern/golog v0.0.0-20230503153817-8e72de7e0a65 github.com/getlantern/hidden v0.0.0-20220104173330-f221c5a24770 github.com/getlantern/i18n v0.0.0-20181205222232-2afc4f49bb1c diff --git a/go.sum b/go.sum index 2516ba23f..dc8dd8cb9 100644 --- a/go.sum +++ b/go.sum @@ -293,12 +293,12 @@ github.com/getlantern/fdcount v0.0.0-20210503151800-5decd65b3731/go.mod h1:XZwE+ github.com/getlantern/filepersist v0.0.0-20160317154340-c5f0cd24e799/go.mod h1:8DGAx0LNUfXNnEH+fXI0s3OCBA/351kZCiz/8YSK3i8= github.com/getlantern/filepersist v0.0.0-20210901195658-ed29a1cb0b7c h1:mcz27xtAkb1OuOLBct/uFfL1p3XxAIcFct82GbT+UZM= github.com/getlantern/filepersist v0.0.0-20210901195658-ed29a1cb0b7c/go.mod h1:8DGAx0LNUfXNnEH+fXI0s3OCBA/351kZCiz/8YSK3i8= -github.com/getlantern/flashlight/v7 v7.6.116 h1:qDG0r464mNIHaFJ8D9nuqBcHP/aNrKYKE74wyUHvOUQ= -github.com/getlantern/flashlight/v7 v7.6.116/go.mod h1:xdGo+HA0bEhTLSCyOhAsx5dTQkipYw24qD/lHZq1pgc= +github.com/getlantern/flashlight/v7 v7.6.118 h1:Vy7sRHqOlmfs5OwffVjSpMu8bb5VPWEYxmCA336StH0= +github.com/getlantern/flashlight/v7 v7.6.118/go.mod h1:Gow436A4ByCIEZOUL538PXamMKWbpJJDNTZgDCtWYuE= github.com/getlantern/framed v0.0.0-20190601192238-ceb6431eeede h1:yrU6Px3ZkvCsDLPryPGi6FN+2iqFPq+JeCb7EFoDBhw= github.com/getlantern/framed v0.0.0-20190601192238-ceb6431eeede/go.mod h1:nhnoiS6DE6zfe+BaCMU4YI01UpsuiXnDqM5S8jxHuuI= -github.com/getlantern/fronted v0.0.0-20240822220559-6e97652d23cc h1:rVEoCLKM16Pfnz8CguT4U75m6o0QDN7stP+MBFUkmNk= -github.com/getlantern/fronted v0.0.0-20240822220559-6e97652d23cc/go.mod h1:WByj7b55hNRpeuIaES521Poebt0ABOdzG/9g+bS4BiQ= +github.com/getlantern/fronted v0.0.0-20241018162326-8501716d3eb0 h1:+jKtFO1Vki3Fa87+UUgsnHttimMnHXtQE4ZCutRgnWE= +github.com/getlantern/fronted v0.0.0-20241018162326-8501716d3eb0/go.mod h1:WByj7b55hNRpeuIaES521Poebt0ABOdzG/9g+bS4BiQ= github.com/getlantern/geo v0.0.0-20240108161311-50692a1b69a9 h1:mSg57/+t59Q08AqArlhW+3N1AVPn5ox0dTOYonRps6w= github.com/getlantern/geo v0.0.0-20240108161311-50692a1b69a9/go.mod h1:RjQ0krF8NTCc5xo2Q1995/vZBnYg33h8svn15do7dLg= github.com/getlantern/geolookup v0.0.0-20230327091034-aebe73c6eef4 h1:Ju9l1RretVWJTNo2vpl/xAW8Dcuiyg5kJC6LRBpCigw= From bd532ac5e6c80cdd059d8c9c4fd9afb948c3dfbe Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Fri, 18 Oct 2024 11:36:46 -0600 Subject: [PATCH 2/8] Adding test to satisfy goveralls --- desktop/ws/service_test.go | 40 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 desktop/ws/service_test.go diff --git a/desktop/ws/service_test.go b/desktop/ws/service_test.go new file mode 100644 index 000000000..1bd62f44d --- /dev/null +++ b/desktop/ws/service_test.go @@ -0,0 +1,40 @@ +package ws + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRegisterWithMsgInitializer(t *testing.T) { + uiChan := NewUIChannel().(*uiChannel) + + helloFn := func(send func(interface{})) { + send("hello") + } + + newMsgFn := func() interface{} { + return "new message" + } + + // Test successful registration + service, err := uiChan.RegisterWithMsgInitializer("testType", helloFn, newMsgFn) + assert.NoError(t, err) + assert.NotNil(t, service) + assert.Equal(t, "testType", service.Type) + assert.NotNil(t, service.in) + assert.NotNil(t, service.out) + assert.NotNil(t, service.stopCh) + + // Test duplicate registration + assert.Panics(t, func() { + _, _ = uiChan.RegisterWithMsgInitializer("testType", helloFn, newMsgFn) + }) + + // Test if the service is correctly added to the services map + uiChan.muServices.RLock() + registeredService := uiChan.services["testType"] + uiChan.muServices.RUnlock() + assert.NotNil(t, registeredService) + assert.Equal(t, service, registeredService) +} From 0b3c1c3afb1958a5380f7afcd63692c3853da2a4 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Fri, 18 Oct 2024 11:53:17 -0600 Subject: [PATCH 3/8] Use internalsdk analytics --- desktop/analytics/analytics.go | 193 ---------------------------- desktop/analytics/analytics_test.go | 105 --------------- desktop/analytics/engine/engine.go | 20 --- desktop/analytics/engine/google.go | 64 --------- desktop/analytics/engine/matomo.go | 63 --------- desktop/app/app.go | 35 ++--- desktop/notifier/notifier.go | 19 +-- desktop/notifier/notifier_test.go | 28 +--- 8 files changed, 10 insertions(+), 517 deletions(-) delete mode 100644 desktop/analytics/analytics.go delete mode 100644 desktop/analytics/analytics_test.go delete mode 100644 desktop/analytics/engine/engine.go delete mode 100644 desktop/analytics/engine/google.go delete mode 100644 desktop/analytics/engine/matomo.go diff --git a/desktop/analytics/analytics.go b/desktop/analytics/analytics.go deleted file mode 100644 index ee4f4cb4d..000000000 --- a/desktop/analytics/analytics.go +++ /dev/null @@ -1,193 +0,0 @@ -package analytics - -import ( - "bytes" - "net/http" - "net/http/httputil" - "net/url" - "os" - "strconv" - "sync" - "time" - - "github.com/getlantern/flashlight/v7/common" - "github.com/getlantern/flashlight/v7/proxied" - "github.com/getlantern/flashlight/v7/util" - "github.com/getlantern/golog" - - "github.com/getlantern/lantern-client/desktop/analytics/engine" -) - -var ( - log = golog.LoggerFor("lantern-desktop.analytics") - - // GA and Matomo ends a session after 30 minutes of inactivity. Prevent it by sending - // keepalive. Note that the session still ends at the midnight. See - // https://support.google.com/analytics/answer/2731565?hl=en - // https://help.piwik.pro/support/questions/how-is-a-session-counted-in-piwik-pro/ - keepaliveInterval = 25 * time.Minute -) - -// Start starts the analytics session with the given data. -func Start(deviceID, version string) *session { - s := newSession(deviceID, version, keepaliveInterval, proxied.ChainedThenFronted()) - go s.keepalive() - return s -} - -type Session interface { - SetIP(string) - EventWithLabel(string, string, string) - Event(string, string) - End() -} - -type NullSession struct{} - -func (s NullSession) SetIP(string) {} -func (s NullSession) EventWithLabel(string, string, string) {} -func (s NullSession) Event(string, string) {} -func (s NullSession) End() {} - -type session struct { - vals url.Values - muVals sync.RWMutex - rt http.RoundTripper - keepaliveInterval time.Duration - chDoneTracking chan struct{} - engine engine.Engine -} - -func newSession(deviceID, version string, keepalive time.Duration, rt http.RoundTripper) *session { - eng := engine.New() - return &session{ - vals: eng.GetSessionValues(version, deviceID, getExecutableHash()), - rt: rt, - keepaliveInterval: keepalive, - chDoneTracking: make(chan struct{}), - engine: eng, - } -} - -// SetIP sets the client IP for better analysis. The IP is always anonymized by -// both Google and matomo engines -func (s *session) SetIP(ip string) { - s.muVals.Lock() - s.vals = s.engine.SetIP(s.vals, ip) - s.muVals.Unlock() - go s.track() -} - -// EventWithLabel tells engine that some event happens in the current page with a label -func (s *session) EventWithLabel(category, action, label string) { - s.muVals.Lock() - s.vals = s.engine.SetEventWithLabel(s.vals, category, action, label) - s.muVals.Unlock() - go s.track() -} - -// Event tells engine that some event happens in the current page. -func (s *session) Event(category, action string) { - s.EventWithLabel(category, action, "") -} - -// End tells engine to force end the current session. -func (s *session) End() { - s.muVals.Lock() - s.vals = s.engine.End(s.vals) - s.muVals.Unlock() - go s.track() -} - -// keepalive keeps tracking session with the latest parameters to avoid engine from -// ending the session. -func (s *session) keepalive() { - keepaliveTimer := time.NewTimer(s.keepaliveInterval) - for { - select { - case <-s.chDoneTracking: - // Note this does not drain the channel before resetting, so - // keepalive may be sent more than required, but that is okay. - keepaliveTimer.Reset(s.keepaliveInterval) - case <-keepaliveTimer.C: - go s.track() - } - } -} - -func (s *session) track() { - s.muVals.RLock() - args := s.vals.Encode() - s.muVals.RUnlock() - defer func() { - select { - case s.chDoneTracking <- struct{}{}: - default: - // tests may not have keepalive loop to receive from the channel - } - }() - - r, err := http.NewRequest("POST", s.engine.GetEndpoint(), bytes.NewBufferString(args)) - if err != nil { - _ = log.Errorf("Error constructing analytics request: %s", err) - return - } - - r.Header.Add("Content-Type", "application/x-www-form-urlencoded") - r.Header.Add("Content-Length", strconv.Itoa(len(args))) - - if req, er := httputil.DumpRequestOut(r, true); er != nil { - log.Debugf("Could not dump request: %v", er) - } else { - log.Debugf("Full analytics request: %v", string(req)) - } - - resp, err := s.rt.RoundTrip(r) - if err != nil { - _ = log.Errorf("Could not send HTTP request to analytics: %s", err) - return - } - log.Debugf("Successfully sent request to analytics: %s", resp.Status) - if err := resp.Body.Close(); err != nil { - log.Debugf("Unable to close response body: %v", err) - } -} - -// getExecutableHash returns the hash of the currently running executable. -// If there's an error getting the hash, this returns -func getExecutableHash() string { - // We don't know how to get a useful hash here for Android but also this - // code isn't currently called on Android, so just guard against something - // bad happening here. - if common.Platform == "android" { - return "android" - } - if lanternPath, err := os.Executable(); err != nil { - log.Debugf("Could not get path to executable %v", err) - return err.Error() - } else { - if b, er := util.GetFileHash(lanternPath); er != nil { - return er.Error() - } else { - return b - } - } -} - -// AddCampaign adds Google Analytics campaign tracking to a URL and returns -// that URL. -func AddCampaign(urlStr, campaign, content, medium string) (string, error) { - u, err := url.Parse(urlStr) - if err != nil { - log.Errorf("Could not parse click URL: %v", err) - return "", err - } - - q := u.Query() - q.Set("utm_source", common.Platform) - q.Set("utm_medium", medium) - q.Set("utm_campaign", campaign) - q.Set("utm_content", content) - u.RawQuery = q.Encode() - return u.String(), nil -} diff --git a/desktop/analytics/analytics_test.go b/desktop/analytics/analytics_test.go deleted file mode 100644 index e6830e1d6..000000000 --- a/desktop/analytics/analytics_test.go +++ /dev/null @@ -1,105 +0,0 @@ -package analytics - -import ( - "bytes" - "errors" - "io/ioutil" - "net/http" - "strings" - "sync/atomic" - "testing" - "time" - - "github.com/getlantern/flashlight/v7/common" - - "github.com/stretchr/testify/assert" -) - -type errorTripper struct { - request *http.Request -} - -func (et *errorTripper) RoundTrip(r *http.Request) (*http.Response, error) { - et.request = r - return nil, errors.New("error") -} - -type successTripper struct { - numRequests int32 - request *http.Request -} - -func (st *successTripper) RoundTrip(r *http.Request) (*http.Response, error) { - st.request = r - atomic.AddInt32(&st.numRequests, 1) - t := &http.Response{ - Body: ioutil.NopCloser(bytes.NewBufferString("Hello World")), - } - - return t, nil -} - -func TestRoundTrip(t *testing.T) { - et := &errorTripper{} - session := newSession("1", "2.2.0", 0, et) - session.track() - assert.Equal(t, "application/x-www-form-urlencoded", et.request.Header.Get("Content-Type"), "unexpected content type") - - st := &successTripper{} - session.rt = st - session.track() - assert.Equal(t, "application/x-www-form-urlencoded", st.request.Header.Get("Content-Type"), "unexpected content type") -} - -func TestKeepalive(t *testing.T) { - st := &successTripper{} - session := newSession("1", "2.2.0", 100*time.Millisecond, st) - go session.keepalive() - time.Sleep(110 * time.Millisecond) - assert.EqualValues(t, 1, atomic.LoadInt32(&st.numRequests), "Should have sent keepalive after the inteval") - time.Sleep(20 * time.Millisecond) - session.Event("category", "action") - // have to wait because event is sent asynchronously - time.Sleep(10 * time.Millisecond) - assert.EqualValues(t, 2, atomic.LoadInt32(&st.numRequests), "Should have sent event") - time.Sleep(80 * time.Millisecond) - assert.EqualValues(t, 2, atomic.LoadInt32(&st.numRequests), "Other requests should reset the keepalive timer") - time.Sleep(30 * time.Millisecond) - assert.EqualValues(t, 3, atomic.LoadInt32(&st.numRequests), "Should have sent another keepalive after the new timer expired") -} - -func TestAnalytics(t *testing.T) { - session := newSession("1", "2.2.0", 0, http.DefaultTransport) - session.SetIP("127.0.0.1") - - argString := session.vals.Encode() - assert.True(t, strings.Contains(argString, "pageview")) - assert.True(t, strings.Contains(argString, "127.0.0.1")) - - // Now actually hit the GA debug server to validate the hit. - url := "https://www.google-analytics.com/debug/collect?" + argString - resp, err := http.Get(url) - if !assert.NoError(t, err, "Should have no error") { - return - } - - defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - assert.Nil(t, err, "Should be nil") - - assert.True(t, strings.Contains(string(body), "\"valid\": true"), "Should be a valid hit") - - session.End() -} - -func TestAddCampaign(t *testing.T) { - startURL := "https://test.com" - campaignURL, err := AddCampaign(startURL, "test-campaign", "test-content", "test-medium") - assert.NoError(t, err) - assert.Equal(t, "https://test.com?utm_campaign=test-campaign&utm_content=test-content&utm_medium=test-medium&utm_source="+common.Platform, campaignURL) - - // Now test a URL that will produce an error - startURL = ":" - _, err = AddCampaign(startURL, "test-campaign", "test-content", "test-medium") - assert.Error(t, err) -} diff --git a/desktop/analytics/engine/engine.go b/desktop/analytics/engine/engine.go deleted file mode 100644 index d7902b10d..000000000 --- a/desktop/analytics/engine/engine.go +++ /dev/null @@ -1,20 +0,0 @@ -package engine - -import "net/url" - -const useMatomo = false - -type Engine interface { - End(vals url.Values) url.Values - GetEndpoint() string - SetIP(vals url.Values, ip string) url.Values - SetEventWithLabel(vals url.Values, category, action, label string) url.Values - GetSessionValues(version, deviceID, execHash string) url.Values -} - -func New() Engine { - if useMatomo { - return NewMatomo() - } - return NewGA() -} diff --git a/desktop/analytics/engine/google.go b/desktop/analytics/engine/google.go deleted file mode 100644 index 268200b24..000000000 --- a/desktop/analytics/engine/google.go +++ /dev/null @@ -1,64 +0,0 @@ -package engine - -import ( - "net/url" - - "github.com/getlantern/flashlight/v7/common" -) - -const ( - // endpoint is the endpoint to report GA data to. - gaEndpoint = `https://ssl.google-analytics.com/collect` -) - -type googleAnalytics struct{} - -func NewGA() Engine { - return &googleAnalytics{} -} - -func (ga googleAnalytics) GetEndpoint() string { - return gaEndpoint -} - -func (ga googleAnalytics) SetIP(vals url.Values, ip string) url.Values { - vals.Set("uip", ip) - return vals -} - -func (ga googleAnalytics) SetEventWithLabel(vals url.Values, category, action, label string) url.Values { - vals.Set("ec", category) - vals.Set("ea", action) - vals.Set("el", label) - vals.Set("t", "event") - return vals -} - -func (ga googleAnalytics) End(vals url.Values) url.Values { - vals.Add("sc", "end") - return vals -} - -func (ga googleAnalytics) GetSessionValues(version, clientID, execHash string) url.Values { - vals := make(url.Values) - - vals.Add("v", "1") - vals.Add("cid", clientID) - vals.Add("tid", common.TrackingID) - - // Make call to anonymize the user's IP address -- basically a policy thing - // where Google agrees not to store it. - vals.Add("aip", "1") - - // Custom dimension for the Lantern version - vals.Add("cd1", version) - - // Custom dimension for the hash of the executable. We combine the version - // to make it easier to interpret in GA. - vals.Add("cd2", version+"-"+execHash) - - vals.Add("dp", "localhost") - vals.Add("t", "pageview") - - return vals -} diff --git a/desktop/analytics/engine/matomo.go b/desktop/analytics/engine/matomo.go deleted file mode 100644 index e4d3564a8..000000000 --- a/desktop/analytics/engine/matomo.go +++ /dev/null @@ -1,63 +0,0 @@ -package engine - -import ( - "net/url" -) - -const ( - // endpoint is the endpoint to report Matomo data to. - matomoEndpoint = `https://analytics.lantern.io/matomo.php` // - this should be in an env - matomoIDSite = "1" // lantern.io - this should be in an env - matomoAuthToken = "9da6ac71d684a0cc5dd3c36034699859" // this should be in an env -) - -type matomo struct{} - -func NewMatomo() Engine { - return &matomo{} -} - -func (ga matomo) GetEndpoint() string { - return matomoEndpoint -} - -func (ga matomo) SetIP(vals url.Values, ip string) url.Values { - vals.Set("cip", ip) - return vals -} - -func (ga matomo) SetEventWithLabel(vals url.Values, category, action, label string) url.Values { - vals.Set("e_c", category) - vals.Set("e_a", action) - vals.Set("e_n", label) - vals.Set("action_name", "event") - return vals -} - -func (ga matomo) End(vals url.Values) url.Values { - vals.Add("new_visit", "1") - return vals -} - -func (ga matomo) GetSessionValues(version, clientID, execHash string) url.Values { - vals := make(url.Values) - - vals.Add("apiv", "1") - vals.Add("rec", "1") - - vals.Add("cid", clientID) - vals.Add("idsite", matomoIDSite) - vals.Add("token_auth", matomoAuthToken) - - // Custom dimension for the Lantern version - vals.Add("dimension1", version) - - // Custom dimension for the hash of the executable. We combine the version - // to make it easier to interpret in GA. - vals.Add("dimension2", version+"-"+execHash) - - vals.Add("url", "localhost") - vals.Add("action_name", "pageview") - - return vals -} diff --git a/desktop/app/app.go b/desktop/app/app.go index 2c6c33be1..845d8c9e8 100644 --- a/desktop/app/app.go +++ b/desktop/app/app.go @@ -32,8 +32,6 @@ import ( "github.com/getlantern/osversion" "github.com/getlantern/profiling" - "github.com/getlantern/lantern-client/desktop/analytics" - "github.com/getlantern/lantern-client/desktop/autoupdate" "github.com/getlantern/lantern-client/desktop/datacap" "github.com/getlantern/lantern-client/desktop/settings" @@ -65,13 +63,12 @@ type App struct { fetchedProxiesConfig atomic.Bool hasSucceedingProxy atomic.Bool - Flags flashlight.Flags - configDir string - exited eventual.Value - analyticsSession analytics.Session - settings *settings.Settings - configService *configService - statsTracker *statsTracker + Flags flashlight.Flags + configDir string + exited eventual.Value + settings *settings.Settings + configService *configService + statsTracker *statsTracker muExitFuncs sync.RWMutex exitFuncs []func() @@ -104,14 +101,12 @@ type App struct { // NewApp creates a new desktop app that initializes the app and acts as a moderator between all desktop components. func NewApp(flags flashlight.Flags, configDir string) *App { ss := settings.LoadSettings(configDir) - analyticsSession := newAnalyticsSession(ss) statsTracker := NewStatsTracker() app := &App{ Flags: flags, configDir: configDir, exited: eventual.NewValue(), settings: ss, - analyticsSession: analyticsSession, connectionStatusCallbacks: make([]func(isConnected bool), 0), selectedTab: VPNTab, configService: new(configService), @@ -125,7 +120,6 @@ func NewApp(flags flashlight.Flags, configDir string) *App { } golog.OnFatal(app.exitOnFatal) - app.AddExitFunc("stopping analytics", app.analyticsSession.End) app.onProStatusChange(func(isPro bool) { statsTracker.SetIsPro(isPro) }) @@ -142,18 +136,6 @@ func NewApp(flags flashlight.Flags, configDir string) *App { return app } -func newAnalyticsSession(settings *settings.Settings) analytics.Session { - if settings.IsAutoReport() { - session := analytics.Start(settings.GetDeviceID(), common.ApplicationVersion) - go func() { - session.SetIP(geolookup.GetIP(eventual.Forever)) - }() - return session - } else { - return analytics.NullSession{} - } -} - // Run starts the app. func (app *App) Run(ctx context.Context) { golog.OnFatal(app.exitOnFatal) @@ -228,7 +210,8 @@ func (app *App) Run(ctx context.Context) { app.IsPro, settings.GetLanguage, func(addr string) (string, error) { return addr, nil }, // no dnsgrab reverse lookups on desktop - app.analyticsSession.EventWithLabel, + // Dummy analytics function + func(category, action, label string) {}, flashlight.WithOnConfig(app.onConfigUpdate), flashlight.WithOnProxies(app.onProxiesUpdate), ) @@ -322,14 +305,12 @@ func (app *App) beforeStart(ctx context.Context, listenAddr string) { // Connect turns on proxying func (app *App) Connect() { - app.analyticsSession.Event("systray-menu", "connect") ops.Begin("connect").End() app.settings.SetDisconnected(false) } // Disconnect turns off proxying func (app *App) Disconnect() { - app.analyticsSession.Event("systray-menu", "disconnect") ops.Begin("disconnect").End() app.settings.SetDisconnected(true) } diff --git a/desktop/notifier/notifier.go b/desktop/notifier/notifier.go index ba37babf6..d08f8c28a 100644 --- a/desktop/notifier/notifier.go +++ b/desktop/notifier/notifier.go @@ -5,10 +5,9 @@ import ( "github.com/getlantern/golog" "github.com/getlantern/i18n" + "github.com/getlantern/lantern-client/internalsdk/analytics" notify "github.com/getlantern/notifier" "github.com/getsentry/sentry-go" - - "github.com/getlantern/lantern-client/desktop/analytics" ) const ( @@ -30,11 +29,6 @@ var ( // and waits for the result. func ShowNotification(note *notify.Notification, campaign string) bool { log.Debug("Showing notification") - err := normalizeClickURL(note, campaign) - if err != nil { - log.Errorf("Could not normalize click URL: %v", err) - return false - } chResult := make(chan bool) ch <- notifierRequest{ note, @@ -45,17 +39,6 @@ func ShowNotification(note *notify.Notification, campaign string) bool { return <-chResult } -func normalizeClickURL(note *notify.Notification, campaign string) error { - ga, err := analytics.AddCampaign(note.ClickURL, campaign, note.Title+"-"+note.Message, "notification") - if err != nil { - log.Errorf("Could not add campaign: %v", err) - return err - } - - note.ClickURL = ga - return nil -} - // NotificationsLoop starts a goroutine to show the desktop notifications // submitted by showNotification one by one with a minimum 10 seconds interval. // diff --git a/desktop/notifier/notifier_test.go b/desktop/notifier/notifier_test.go index db66d24a8..81ba8c33a 100644 --- a/desktop/notifier/notifier_test.go +++ b/desktop/notifier/notifier_test.go @@ -6,11 +6,9 @@ import ( "github.com/stretchr/testify/assert" - "github.com/getlantern/golog" notify "github.com/getlantern/notifier" - "github.com/getlantern/flashlight/v7/common" - "github.com/getlantern/lantern-client/desktop/analytics" + "github.com/getlantern/lantern-client/internalsdk/analytics" ) func TestNotify(t *testing.T) { @@ -30,27 +28,3 @@ func TestNotify(t *testing.T) { assert.True(t, shown) stop() } - -func TestNormalizeClickURL(t *testing.T) { - log := golog.LoggerFor("lantern-desktop.notifier-test") - note := ¬ify.Notification{ - Title: "test", - Message: "test", - ClickURL: "https://test.com", - IconURL: "https://test.com", - } - - err := normalizeClickURL(note, "test-campaign") - assert.NoError(t, err, "unexpected error") - log.Debugf("url: %v", note.ClickURL) - assert.Equal(t, "https://test.com?utm_campaign=test-campaign&utm_content=test-test&utm_medium=notification&utm_source="+common.Platform, note.ClickURL) - - note.ClickURL = ":" - log.Debugf("url: %v", note.ClickURL) - err = normalizeClickURL(note, "test-campaign") - assert.Error(t, err, "expected an error") - - stop := loopFor(10*time.Millisecond, analytics.NullSession{}) - defer stop() - assert.False(t, ShowNotification(note, "test-campaign")) -} From d085acfc9fcc25e2ce282eea864bc3e3a57f8334 Mon Sep 17 00:00:00 2001 From: atavism Date: Mon, 21 Oct 2024 11:41:11 -0700 Subject: [PATCH 4/8] macos code signing updates (#1218) --- .github/workflows/build-darwin.yml | 4 ++-- Makefile | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-darwin.yml b/.github/workflows/build-darwin.yml index 2975c117a..c1a5a7230 100644 --- a/.github/workflows/build-darwin.yml +++ b/.github/workflows/build-darwin.yml @@ -131,8 +131,8 @@ jobs: make package-darwin env: VERSION: "${{ env.version }}" - MACOS_CERTIFICATE: ${{ secrets.MACOS_CERT }} - MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERT_PASS }} + MACOS_CERTIFICATE: ${{ secrets.MACOS_BNS_CERT }} + MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_BNS_CERT_PASS }} - name: Install s3cmd run: pip install s3cmd diff --git a/Makefile b/Makefile index 9dbec78e0..fba598817 100644 --- a/Makefile +++ b/Makefile @@ -230,7 +230,7 @@ tag: require-version git push define osxcodesign - codesign --options runtime --strict --timestamp --force --deep -s "Developer ID Application: Innovate Labs LLC (4FYC28AXA2)" -v $(1) + codesign --options runtime --strict --timestamp --force --deep -s "Developer ID Application: Brave New Software Project, Inc (ACZRKC3LQ9)" -v $(1) endef guard-%: From 03c5c4f0c118d5985733f250e023dded89bf1635 Mon Sep 17 00:00:00 2001 From: jigar-f <132374182+jigar-f@users.noreply.github.com> Date: Tue, 22 Oct 2024 15:47:57 +0530 Subject: [PATCH 5/8] Update CI to use TapSell keys. (#1219) --- .github/workflows/build-android.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index 6df251ace..ef8be027d 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -99,10 +99,14 @@ jobs: env: ANDROID_INTERSTITIAL_AD_ID: ${{ secrets.INTERSTITIAL_AD_UNIT_ID }} IOS_INTERSTITIAL_AD_ID: ${{ secrets.INTERSTITIAL_AD_UNIT_ID_IOS }} + TAPSELL_VIDEO_INTERSTITIAL_ZONE_ID: ${{ secrets.TAPSELL_VIDEO_INTERSTITIAL_ZONE_ID }} + TAPSELL_INTERSTITIAL_ZONE_ID: ${{ secrets.TAPSELL_INTERSTITIAL_ZONE_ID }} run: | touch app.env echo "Android_interstitialAd=$ANDROID_INTERSTITIAL_AD_ID" > app.env echo "IOS_interstitialAd=$IOS_INTERSTITIAL_AD_ID" >> app.env + echo "VideoInterstitialZoneId=$TAPSELL_VIDEO_INTERSTITIAL_ZONE_ID" >> app.env + echo "InterstitialZoneId=$TAPSELL_INTERSTITIAL_ZONE_ID" >> app.env - name: Build Android installers env: From 66b506e7299da0f3f9b82defb069818f20157477 Mon Sep 17 00:00:00 2001 From: jigar-f <132374182+jigar-f@users.noreply.github.com> Date: Tue, 22 Oct 2024 21:33:16 +0530 Subject: [PATCH 6/8] Address Crashes in 7.9.8 Beta Build (#1217) * Fix service null issue. * Fixe Plausible issue * add try catch on proxy controller. * Fix context issue on link device. * Fix manifest issue. * Fix google play crash. * Fix memory issue and apply UI improvements * PR review changes. * Refactor code to coroutine to prevent ANR --- android/app/src/main/AndroidManifest.xml | 14 +- .../main/kotlin/io/lantern/apps/AppData.kt | 29 +++-- .../io/lantern/apps/AppsDataProvider.kt | 8 ++ .../kotlin/io/lantern/model/SessionModel.kt | 26 +++- .../org/getlantern/lantern/LanternApp.kt | 10 +- .../org/getlantern/lantern/MainActivity.kt | 32 +++-- .../lantern/service/LanternService.kt | 7 +- .../lantern/service/ServiceHelper.kt | 20 ++- .../getlantern/lantern/util/PaymentsUtil.kt | 123 ++++++++++-------- assets/locales/en-us.po | 3 + internalsdk/session_model.go | 6 +- internalsdk/survey.go | 2 +- lib/core/app/app_webview.dart | 30 +++-- lib/core/extension/error_extension.dart | 15 ++- lib/features/account/split_tunneling.dart | 3 +- lib/features/checkout/play_checkout.dart | 4 +- lib/features/device_linking/link_device.dart | 9 +- 17 files changed, 216 insertions(+), 125 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 259d0c279..c8944d51f 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -34,14 +34,14 @@ android:name="org.getlantern.lantern.LanternApp" android:allowBackup="false" android:hardwareAccelerated="true" - android:resizeableActivity="false" android:icon="@drawable/app_icon" android:label="@string/app_name" + android:largeHeap="true" android:networkSecurityConfig="@xml/network_security_config" android:persistent="true" android:requestLegacyExternalStorage="true" + android:resizeableActivity="false" android:theme="@style/AppTheme" - android:largeHeap="true" tools:replace="allowBackup, label" tools:targetApi="n"> @@ -88,14 +88,20 @@ + + + - + { + val props: Map = mapOf("title" to call.argument("title")!!) + Plausible.event( + call.argument("name")!!, url = call.argument("url")!!, props = props + ) + } + else -> super.doOnMethodCall(call, result) } } @@ -199,7 +208,10 @@ class SessionModel internal constructor( } fun setUserIdAndToken(userId: Long, token: String) { - model.invokeMethod("setUserIdAndToken", Arguments(mapOf("userId" to userId, "token" to token))) + model.invokeMethod( + "setUserIdAndToken", + Arguments(mapOf("userId" to userId, "token" to token)) + ) } fun setUserPro(isPro: Boolean) { @@ -381,20 +393,26 @@ class SessionModel internal constructor( // this ends up in memory out of exception CoroutineScope(Dispatchers.IO).launch { try { + val start = System.currentTimeMillis() val appsList = appsDataProvider.listOfApps() // First add just the app names to get a list quickly val apps = buildJsonArray { appsList.forEach { app -> add( buildJsonObject { - val byte = ByteString.copyFrom(app.icon) + val byte = app.icon!! put("packageName", app.packageName) put("name", app.name) - put("icon", byte.toByteArray().toUByteArray().joinToString(", ")) + putJsonArray("icon") { + byte.toUByteArray().forEach { add(it.toInt()) } + } } ) } } + val end = System.currentTimeMillis() + Logger.debug(TAG, "Time taken to get app data: ${end - start} ms") + model.invokeMethod( "updateAppsData", Arguments(mapOf("appsList" to apps.toString())) diff --git a/android/app/src/main/kotlin/org/getlantern/lantern/LanternApp.kt b/android/app/src/main/kotlin/org/getlantern/lantern/LanternApp.kt index dd01dc05b..951cf5944 100644 --- a/android/app/src/main/kotlin/org/getlantern/lantern/LanternApp.kt +++ b/android/app/src/main/kotlin/org/getlantern/lantern/LanternApp.kt @@ -57,6 +57,7 @@ open class LanternApp : Application() { private lateinit var appContext: Context private lateinit var inAppBilling: InAppBilling lateinit var session: SessionModel + val sessionInitialized = ::session.isInitialized private lateinit var goSession: internalsdk.SessionModel var messaging: MessagingHolder = MessagingHolder() @@ -69,9 +70,10 @@ open class LanternApp : Application() { fun getInAppBilling(): InAppBilling { return inAppBilling } + @JvmStatic - fun setInAppBilling(inAppBilling: InAppBilling) { - this.inAppBilling= inAppBilling + fun setInAppBilling(inAppBilling: InAppBilling) { + this.inAppBilling = inAppBilling } @@ -81,8 +83,8 @@ open class LanternApp : Application() { } @JvmStatic - fun setGoSession(sessionModel :internalsdk.SessionModel) { - goSession= sessionModel + fun setGoSession(sessionModel: internalsdk.SessionModel) { + goSession = sessionModel } } } diff --git a/android/app/src/main/kotlin/org/getlantern/lantern/MainActivity.kt b/android/app/src/main/kotlin/org/getlantern/lantern/MainActivity.kt index 4276b705a..78d789bc1 100644 --- a/android/app/src/main/kotlin/org/getlantern/lantern/MainActivity.kt +++ b/android/app/src/main/kotlin/org/getlantern/lantern/MainActivity.kt @@ -193,18 +193,8 @@ class MainActivity : } override fun onResume() { - val start = System.currentTimeMillis() super.onResume() - - val isServiceRunning = Utils.isServiceRunning(activity, LanternVpnService::class.java) - if (vpnModel.isConnectedToVpn() && !isServiceRunning) { - Logger.d(TAG, "LanternVpnService isn't running, clearing VPN preference") - vpnModel.setVpnOn(false) - } else if (!vpnModel.isConnectedToVpn() && isServiceRunning) { - Logger.d(TAG, "LanternVpnService is running, updating VPN preference") - vpnModel.setVpnOn(true) - } - Logger.debug(TAG, "onResume() finished at ${System.currentTimeMillis() - start}") + checkVPNStatus(); } override fun onDestroy() { @@ -233,6 +223,24 @@ class MainActivity : } } + private fun checkVPNStatus() { + CoroutineScope(Dispatchers.IO).launch { + val start = System.currentTimeMillis() + val isServiceRunning = withContext(Dispatchers.Main) { + Utils.isServiceRunning(activity, LanternVpnService::class.java) + } + + if (vpnModel.isConnectedToVpn() && !isServiceRunning) { + Logger.d(TAG, "LanternVpnService isn't running, clearing VPN preference") + vpnModel.setVpnOn(false) + } else if (!vpnModel.isConnectedToVpn() && isServiceRunning) { + Logger.d(TAG, "LanternVpnService is running, updating VPN preference") + vpnModel.setVpnOn(true) + } + Logger.debug(TAG, "onResume() finished at ${System.currentTimeMillis() - start}") + } + + } private fun startLanternService() { try { @@ -279,7 +287,7 @@ class MainActivity : sendSurveyEvent(it) } } catch (e: Exception) { - Logger.error("Survey", "Error fetching loconf", e) + Logger.warn("Survey", "Error fetching loconf", e) } }, 2000L) } diff --git a/android/app/src/main/kotlin/org/getlantern/lantern/service/LanternService.kt b/android/app/src/main/kotlin/org/getlantern/lantern/service/LanternService.kt index 3612bc32e..413f21f52 100644 --- a/android/app/src/main/kotlin/org/getlantern/lantern/service/LanternService.kt +++ b/android/app/src/main/kotlin/org/getlantern/lantern/service/LanternService.kt @@ -42,12 +42,7 @@ open class LanternService : Service(), Runnable { override fun onCreate() { super.onCreate() - val serviceIcon: Int = if (LanternApp.session.chatEnabled()) { - R.drawable.status_chat - } else { - R.drawable.status_plain - } - helper = ServiceHelper(this, serviceIcon, R.string.ready_to_connect) + helper = ServiceHelper(this, R.string.ready_to_connect) } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { diff --git a/android/app/src/main/kotlin/org/getlantern/lantern/service/ServiceHelper.kt b/android/app/src/main/kotlin/org/getlantern/lantern/service/ServiceHelper.kt index 6e5f3cdc4..947638acf 100644 --- a/android/app/src/main/kotlin/org/getlantern/lantern/service/ServiceHelper.kt +++ b/android/app/src/main/kotlin/org/getlantern/lantern/service/ServiceHelper.kt @@ -22,19 +22,21 @@ import java.util.concurrent.atomic.AtomicBoolean class ServiceHelper( private val service: Service, - private val defaultIcon: Int?, private val defaultText: Int ) { private val foregrounded = AtomicBoolean(false) fun makeForeground() { try { - val serviceIcon = defaultIcon - ?: if (LanternApp.session.chatEnabled()) { + val serviceIcon = if (LanternApp.sessionInitialized) { + if (LanternApp.session.chatEnabled()) { R.drawable.status_chat } else { R.drawable.status_plain } + } else { + R.drawable.status_plain + } if (foregrounded.compareAndSet(false, true)) { val doIt = { service.startForeground( @@ -47,8 +49,18 @@ class ServiceHelper( } } catch (e: Exception) { Logger.debug("ServiceHelper", "Failed to make service foreground", e) - } + if (foregrounded.compareAndSet(false, true)) { + val doIt = { + service.startForeground( + notificationId, + buildNotification(R.drawable.status_plain, defaultText) + ) + } + serviceDeque.push(doIt) + doIt() + } + } } fun onDestroy() { diff --git a/android/app/src/main/kotlin/org/getlantern/lantern/util/PaymentsUtil.kt b/android/app/src/main/kotlin/org/getlantern/lantern/util/PaymentsUtil.kt index 4c0eb2eee..a96ef5fc5 100644 --- a/android/app/src/main/kotlin/org/getlantern/lantern/util/PaymentsUtil.kt +++ b/android/app/src/main/kotlin/org/getlantern/lantern/util/PaymentsUtil.kt @@ -110,73 +110,84 @@ class PaymentsUtil(private val activity: Activity) { planID: String, methodCallResult: MethodChannel.Result, ) { - assert(planID.isNotEmpty(), { "PlanId cannot be empty" }) - val inAppBilling = LanternApp.getInAppBilling() - val plan = getPlanYear(planID) - Logger.debug(TAG, "Starting in-app purchase for plan with ID $plan") - inAppBilling.startPurchase( - activity, - plan, - object : PurchasesUpdatedListener { - override fun onPurchasesUpdated( - billingResult: BillingResult, - purchases: MutableList?, - ) { - if (billingResult.responseCode != BillingClient.BillingResponseCode.OK) { - methodCallResult.error( - "unknownError", - activity.resources.getString(R.string.error_making_purchase), - null, - ) - return - } + try { - val tokens = mutableListOf() - for (purchase in purchases!!) { - if (!purchase.isAcknowledged) tokens.add(purchase.purchaseToken) - } - if (tokens.size != 1) { - Logger.error( - TAG, - "Unexpected number of purchased products, not proceeding with purchase", - ) - methodCallResult.error( - "unknownError", - activity.resources.getString(R.string.error_making_purchase), - null, - ) - return - } + assert(planID.isNotEmpty(), { "PlanId cannot be empty" }) + val inAppBilling = LanternApp.getInAppBilling() + val plan = getPlanYear(planID) + Logger.debug(TAG, "Starting in-app purchase for plan with ID $plan") + inAppBilling.startPurchase( + activity, + plan, + object : PurchasesUpdatedListener { + override fun onPurchasesUpdated( + billingResult: BillingResult, + purchases: MutableList?, + ) { + if (billingResult.responseCode != BillingClient.BillingResponseCode.OK) { + methodCallResult.error( + "unknownError", + activity.resources.getString(R.string.error_making_purchase), + null, + ) + return + } - if (purchases[0].purchaseState != Purchase.PurchaseState.PURCHASED) { - /* + val tokens = mutableListOf() + for (purchase in purchases!!) { + if (!purchase.isAcknowledged) tokens.add(purchase.purchaseToken) + } + + if (tokens.size != 1) { + Logger.error( + TAG, + "Unexpected number of purchased products, not proceeding with purchase", + ) + methodCallResult.error( + "unknownError", + activity.resources.getString(R.string.error_making_purchase), + null, + ) + return + } + + if (purchases[0].purchaseState != Purchase.PurchaseState.PURCHASED) { + /* * if the purchase state is not purchased then do not call api * make user pro temporary next user open app it will check the purchase state and call api accordingly * */ - LanternApp.session.setUserPro(true) - return - } + LanternApp.session.setUserPro(true) + return + } - /* + /* * Important: Google Play payment ignores the app-selected locale and currency * It always uses the device's locale so * We need to pass device local it does not mismatch to server while acknolgment*/ - try { - session.submitGooglePlayPayment(email, planID, tokens.first()) - methodCallResult.success("purchaseSuccessful") - } catch (e: Exception) { - methodCallResult.error( - "errorMakingPurchase", - activity.getString( - R.string.error_making_purchase, - ), - null, - ) + try { + session.submitGooglePlayPayment(email, planID, tokens.first()) + methodCallResult.success("purchaseSuccessful") + } catch (e: Exception) { + methodCallResult.error( + "errorMakingPurchase", + activity.getString( + R.string.error_making_purchase, + ), + null, + ) + } } - } - }, - ) + }, + ) + }catch (t: Exception) { + Logger.error(TAG, "Error submitting to Google Play", t) + methodCallResult.error( + "errorSubmittingToGooglePlay", + activity.getString(R.string.error_making_purchase), + null, + ) + } } companion object { diff --git a/assets/locales/en-us.po b/assets/locales/en-us.po index a878daf59..a5384d30a 100644 --- a/assets/locales/en-us.po +++ b/assets/locales/en-us.po @@ -1833,5 +1833,8 @@ msgstr "Socks Proxy copied" msgid "proxy_everything" msgstr "Proxy Everything" +msgid "errorSubmittingToGooglePlay" +msgstr "We are currently experiencing difficulties processing your request for Google Play. Please try again later." + diff --git a/internalsdk/session_model.go b/internalsdk/session_model.go index e093590a7..3198e6a26 100644 --- a/internalsdk/session_model.go +++ b/internalsdk/session_model.go @@ -2367,7 +2367,7 @@ func (session *SessionModel) appsAllowedAccess() (string, error) { type AppInfo struct { PackageName string `json:"packageName"` Name string `json:"name"` - Icon string `json:"icon"` + Icon []int `json:"icon"` } func (session *SessionModel) updateAppsData(appsList string) error { @@ -2376,11 +2376,11 @@ func (session *SessionModel) updateAppsData(appsList string) error { if err != nil { log.Fatalf("Error decoding JSON: %v", err) } + return pathdb.Mutate(session.db, func(tx pathdb.TX) error { for _, app := range apps { path := pathAppsData + app.PackageName - list, _ := convertStringArrayToIntArray(strings.Split(app.Icon, ", ")) - imagebyte, _ := convertIntArrayToByteArray(list) + imagebyte, _ := convertIntArrayToByteArray(app.Icon) vpn := &protos.AppData{ PackageName: app.PackageName, Name: app.Name, diff --git a/internalsdk/survey.go b/internalsdk/survey.go index 4e49c9fab..58fdfc239 100644 --- a/internalsdk/survey.go +++ b/internalsdk/survey.go @@ -13,7 +13,7 @@ import ( ) var ( - dialTimeout = 30 * time.Second + dialTimeout = 15 * time.Second httpClient = &http.Client{ Transport: proxied.ParallelPreferChained(), Timeout: dialTimeout, diff --git a/lib/core/app/app_webview.dart b/lib/core/app/app_webview.dart index 8223c61aa..99a9f0dbf 100644 --- a/lib/core/app/app_webview.dart +++ b/lib/core/app/app_webview.dart @@ -75,17 +75,25 @@ class AppBrowser extends InAppBrowser { }); static Future setProxyAddr() async { - var proxyAvailable = - await WebViewFeature.isFeatureSupported(WebViewFeature.PROXY_OVERRIDE); - if (proxyAvailable) { - ProxyController proxyController = ProxyController.instance(); - final proxyAddr = await sessionModel.proxyAddr(); - await proxyController.clearProxyOverride(); - await proxyController.setProxyOverride( - settings: ProxySettings( - proxyRules: [ProxyRule(url: "http://$proxyAddr")], - bypassRules: [], - )); + try { + var proxyAvailable = await WebViewFeature.isFeatureSupported( + WebViewFeature.PROXY_OVERRIDE); + if (proxyAvailable) { + ProxyController proxyController = ProxyController.instance(); + final proxyAddr = await sessionModel.proxyAddr(); + if (proxyAddr.isEmpty) { + return; + } + await proxyController.clearProxyOverride(); + await proxyController.setProxyOverride( + settings: ProxySettings( + proxyRules: [ProxyRule(url: "http://$proxyAddr")], + bypassRules: [], + )); + appLogger.e("Proxy set as :http://$proxyAddr"); + } + } catch (e) { + appLogger.e("Error setting proxy address: $e"); } } diff --git a/lib/core/extension/error_extension.dart b/lib/core/extension/error_extension.dart index e65aee063..8db687f2b 100644 --- a/lib/core/extension/error_extension.dart +++ b/lib/core/extension/error_extension.dart @@ -1,4 +1,3 @@ - import '../utils/common.dart'; extension ErrorX on Object { @@ -33,7 +32,9 @@ extension ErrorX on Object { if (description.contains("wrong-reseller-code")) { return "wrong_seller_code".i18n; } - if (description.contains("user already exists") || description.contains("user with this legacy user ID already exists")) { + if (description.contains("user already exists") || + description + .contains("user with this legacy user ID already exists")) { return "signup_error_user_exists".i18n; } @@ -67,9 +68,13 @@ extension ErrorX on Object { if (description.contains("error while sign up")) { return "signup_error".i18n; - } else { - return 'we_are_experiencing_technical_difficulties'.i18n; } + + if (description.contains("errorSubmittingToGooglePlay")) { + return "errorSubmittingToGooglePlay".i18n; + } + + return 'we_are_experiencing_technical_difficulties'.i18n; } else { return toString().i18n; } @@ -77,4 +82,4 @@ extension ErrorX on Object { return toString().i18n; } } -} \ No newline at end of file +} diff --git a/lib/features/account/split_tunneling.dart b/lib/features/account/split_tunneling.dart index f569761d0..5e4add305 100644 --- a/lib/features/account/split_tunneling.dart +++ b/lib/features/account/split_tunneling.dart @@ -107,8 +107,7 @@ class _SplitTunnelingState extends State { Iterable> _appsData, Widget? child, ) { - return Expanded( - child: SplitTunnelingAppsList(appsList: _appsData.toList())); + return SplitTunnelingAppsList(appsList: _appsData.toList()); }, ); } diff --git a/lib/features/checkout/play_checkout.dart b/lib/features/checkout/play_checkout.dart index 1bfd290f7..87fcc38e9 100644 --- a/lib/features/checkout/play_checkout.dart +++ b/lib/features/checkout/play_checkout.dart @@ -153,9 +153,9 @@ class _PlayCheckoutState extends State showSuccessDialog(context, widget.isPro); } } catch (error, stackTrace) { - // In case of an error, hide the loader and show the error message. context.loaderOverlay.hide(); - showError(context, error: error, stackTrace: stackTrace); + CDialog.showError(context, description: error.localizedDescription); + } } } diff --git a/lib/features/device_linking/link_device.dart b/lib/features/device_linking/link_device.dart index b45a115a9..517e9073c 100644 --- a/lib/features/device_linking/link_device.dart +++ b/lib/features/device_linking/link_device.dart @@ -17,9 +17,12 @@ class _LinkDeviceState extends State { @override void initState() { super.initState(); - if (isMobile()) { - requestLinkCode(); - } + WidgetsBinding.instance.addPostFrameCallback((_) { + if (isMobile()) { + requestLinkCode(); + } + }); + } Future requestLinkCode() async { From 3e95708705d0107a3e7cdfc981cc9c81a8e30851 Mon Sep 17 00:00:00 2001 From: git Date: Wed, 23 Oct 2024 00:26:12 +0000 Subject: [PATCH 7/8] updated translations... --- assets/locales/ar-eg.po | 3 +++ assets/locales/bn-bd.po | 3 +++ assets/locales/es-cu.po | 3 +++ assets/locales/es-es.po | 3 +++ assets/locales/fa-ir.po | 3 +++ assets/locales/fr-ca.po | 3 +++ assets/locales/fr-fr.po | 3 +++ assets/locales/hi-in.po | 3 +++ assets/locales/ms-my.po | 3 +++ assets/locales/my-mm.po | 3 +++ assets/locales/ru-ru.po | 3 +++ assets/locales/th-th.po | 3 +++ assets/locales/tr-tr.po | 3 +++ assets/locales/ur-in.po | 3 +++ assets/locales/vi-vn.po | 3 +++ assets/locales/zh-cn.po | 3 +++ assets/locales/zh-hk.po | 3 +++ 17 files changed, 51 insertions(+) diff --git a/assets/locales/ar-eg.po b/assets/locales/ar-eg.po index a04343794..1c8a80a70 100644 --- a/assets/locales/ar-eg.po +++ b/assets/locales/ar-eg.po @@ -1975,3 +1975,6 @@ msgstr "" msgid "proxy_everything" msgstr "" + +msgid "errorSubmittingToGooglePlay" +msgstr "" diff --git a/assets/locales/bn-bd.po b/assets/locales/bn-bd.po index c3f426bbd..f3ba5e66c 100644 --- a/assets/locales/bn-bd.po +++ b/assets/locales/bn-bd.po @@ -1988,3 +1988,6 @@ msgstr "" msgid "proxy_everything" msgstr "" + +msgid "errorSubmittingToGooglePlay" +msgstr "" diff --git a/assets/locales/es-cu.po b/assets/locales/es-cu.po index c48cad9d2..49c0708a0 100644 --- a/assets/locales/es-cu.po +++ b/assets/locales/es-cu.po @@ -2012,3 +2012,6 @@ msgstr "" msgid "proxy_everything" msgstr "" + +msgid "errorSubmittingToGooglePlay" +msgstr "" diff --git a/assets/locales/es-es.po b/assets/locales/es-es.po index 9b46a5276..4c87c11c4 100644 --- a/assets/locales/es-es.po +++ b/assets/locales/es-es.po @@ -2014,3 +2014,6 @@ msgstr "" msgid "proxy_everything" msgstr "" + +msgid "errorSubmittingToGooglePlay" +msgstr "" diff --git a/assets/locales/fa-ir.po b/assets/locales/fa-ir.po index 3272f8360..2a97a6b97 100644 --- a/assets/locales/fa-ir.po +++ b/assets/locales/fa-ir.po @@ -1974,3 +1974,6 @@ msgstr "" msgid "proxy_everything" msgstr "" + +msgid "errorSubmittingToGooglePlay" +msgstr "" diff --git a/assets/locales/fr-ca.po b/assets/locales/fr-ca.po index 203b9e8be..98b6269f6 100644 --- a/assets/locales/fr-ca.po +++ b/assets/locales/fr-ca.po @@ -2029,3 +2029,6 @@ msgstr "" msgid "proxy_everything" msgstr "" + +msgid "errorSubmittingToGooglePlay" +msgstr "" diff --git a/assets/locales/fr-fr.po b/assets/locales/fr-fr.po index 76253ed76..41e795077 100644 --- a/assets/locales/fr-fr.po +++ b/assets/locales/fr-fr.po @@ -2032,3 +2032,6 @@ msgstr "" msgid "proxy_everything" msgstr "" + +msgid "errorSubmittingToGooglePlay" +msgstr "" diff --git a/assets/locales/hi-in.po b/assets/locales/hi-in.po index fe5a363c3..1dc20c941 100644 --- a/assets/locales/hi-in.po +++ b/assets/locales/hi-in.po @@ -1973,3 +1973,6 @@ msgstr "" msgid "proxy_everything" msgstr "" + +msgid "errorSubmittingToGooglePlay" +msgstr "" diff --git a/assets/locales/ms-my.po b/assets/locales/ms-my.po index 817357e76..33cd03111 100644 --- a/assets/locales/ms-my.po +++ b/assets/locales/ms-my.po @@ -1994,3 +1994,6 @@ msgstr "" msgid "proxy_everything" msgstr "" + +msgid "errorSubmittingToGooglePlay" +msgstr "" diff --git a/assets/locales/my-mm.po b/assets/locales/my-mm.po index f2cb110cf..b2fa5ac6f 100644 --- a/assets/locales/my-mm.po +++ b/assets/locales/my-mm.po @@ -2022,3 +2022,6 @@ msgstr "" msgid "proxy_everything" msgstr "" + +msgid "errorSubmittingToGooglePlay" +msgstr "" diff --git a/assets/locales/ru-ru.po b/assets/locales/ru-ru.po index f7b9c9df1..d48d0a954 100644 --- a/assets/locales/ru-ru.po +++ b/assets/locales/ru-ru.po @@ -1990,3 +1990,6 @@ msgstr "" msgid "proxy_everything" msgstr "" + +msgid "errorSubmittingToGooglePlay" +msgstr "" diff --git a/assets/locales/th-th.po b/assets/locales/th-th.po index 4002a2667..42227719d 100644 --- a/assets/locales/th-th.po +++ b/assets/locales/th-th.po @@ -1957,3 +1957,6 @@ msgstr "" msgid "proxy_everything" msgstr "" + +msgid "errorSubmittingToGooglePlay" +msgstr "" diff --git a/assets/locales/tr-tr.po b/assets/locales/tr-tr.po index 34f2c049c..12f8622e7 100644 --- a/assets/locales/tr-tr.po +++ b/assets/locales/tr-tr.po @@ -1989,3 +1989,6 @@ msgstr "" msgid "proxy_everything" msgstr "" + +msgid "errorSubmittingToGooglePlay" +msgstr "" diff --git a/assets/locales/ur-in.po b/assets/locales/ur-in.po index 1164e9c3b..26c314237 100644 --- a/assets/locales/ur-in.po +++ b/assets/locales/ur-in.po @@ -1988,3 +1988,6 @@ msgstr "" msgid "proxy_everything" msgstr "" + +msgid "errorSubmittingToGooglePlay" +msgstr "" diff --git a/assets/locales/vi-vn.po b/assets/locales/vi-vn.po index d78bee3d3..4e509d14d 100644 --- a/assets/locales/vi-vn.po +++ b/assets/locales/vi-vn.po @@ -1976,3 +1976,6 @@ msgstr "" msgid "proxy_everything" msgstr "" + +msgid "errorSubmittingToGooglePlay" +msgstr "" diff --git a/assets/locales/zh-cn.po b/assets/locales/zh-cn.po index 2c41dc8f9..d78f93850 100644 --- a/assets/locales/zh-cn.po +++ b/assets/locales/zh-cn.po @@ -1877,3 +1877,6 @@ msgstr "" msgid "proxy_everything" msgstr "" + +msgid "errorSubmittingToGooglePlay" +msgstr "" diff --git a/assets/locales/zh-hk.po b/assets/locales/zh-hk.po index 4711fcd95..060baa1e7 100644 --- a/assets/locales/zh-hk.po +++ b/assets/locales/zh-hk.po @@ -1872,3 +1872,6 @@ msgstr "" msgid "proxy_everything" msgstr "" + +msgid "errorSubmittingToGooglePlay" +msgstr "" From f7dcc9f6c26fbba3dbc5090d9d4c38643b5093b5 Mon Sep 17 00:00:00 2001 From: atavism Date: Tue, 22 Oct 2024 20:10:00 -0700 Subject: [PATCH 8/8] Update golang.org/x/mobile and Android CI (#1220) * updates to Android CI * merge latest * update comment --- .github/workflows/build-android.yml | 28 ++++++++++++++++++++++------ go.mod | 14 +++++++------- go.sum | 28 ++++++++++++++-------------- 3 files changed, 43 insertions(+), 27 deletions(-) diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index ef8be027d..fad95e535 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -63,12 +63,6 @@ jobs: java-version: 17 cache: 'gradle' - - name: Clean up disk space - run: | - df -h # Check disk space - sudo apt-get clean - sudo apt-get autoremove - - name: Setup protoc uses: arduino/setup-protoc@v2 with: @@ -108,6 +102,12 @@ jobs: echo "VideoInterstitialZoneId=$TAPSELL_VIDEO_INTERSTITIAL_ZONE_ID" >> app.env echo "InterstitialZoneId=$TAPSELL_INTERSTITIAL_ZONE_ID" >> app.env + - name: Clean packages + run: | + sudo apt-get clean + sudo apt-get autoremove -y + sudo apt-get remove --purge $(dpkg --list | grep '^rc' | awk '{print $2}') + - name: Build Android installers env: INTERSTITIAL_AD_UNIT: "${{ secrets.INTERSTITIAL_AD_UNIT_ID }}" @@ -115,6 +115,22 @@ jobs: VERSION: "${{ env.version }}" run: make package-android + - name: Clean Go module and build cache + run: | + sudo rm -rf /tmp/* + sudo rm -rf /var/cache/* + sudo apt-get clean + sudo apt-get autoremove -y + sudo apt-get remove --purge $(dpkg --list | grep '^rc' | awk '{print $2}') + go clean -modcache + go clean -cache -testcache + df -h + + - name: Delete Gradle cache + run: | + sudo rm -rf $HOME/.gradle/caches + echo "Gradle cache deleted successfully" + - uses: actions/upload-artifact@v4 with: name: android-apk-build diff --git a/go.mod b/go.mod index e3da13173..9a546a096 100644 --- a/go.mod +++ b/go.mod @@ -72,10 +72,10 @@ require ( github.com/moul/http2curl v1.0.0 github.com/shopspring/decimal v1.4.0 github.com/stretchr/testify v1.9.0 - golang.org/x/crypto v0.26.0 - golang.org/x/mobile v0.0.0-20240404231514-09dbf07665ed - golang.org/x/net v0.28.0 - golang.org/x/sys v0.25.0 + golang.org/x/crypto v0.28.0 + golang.org/x/mobile v0.0.0-20241016134751-7ff83004ec2c + golang.org/x/net v0.30.0 + golang.org/x/sys v0.26.0 google.golang.org/protobuf v1.34.2 nhooyr.io/websocket v1.8.17 ) @@ -331,11 +331,11 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect - golang.org/x/mod v0.20.0 // indirect + golang.org/x/mod v0.21.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/text v0.17.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/time v0.6.0 // indirect - golang.org/x/tools v0.24.0 // indirect + golang.org/x/tools v0.26.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240808171019-573a1156607a // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240808171019-573a1156607a // indirect google.golang.org/grpc v1.65.0 // indirect diff --git a/go.sum b/go.sum index dc8dd8cb9..f1ef89698 100644 --- a/go.sum +++ b/go.sum @@ -1039,8 +1039,8 @@ golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98y golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20220428152302-39d4317da171/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= @@ -1050,15 +1050,15 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mobile v0.0.0-20240404231514-09dbf07665ed h1:vZhAhVr5zF1IJaVKTawyTq78WSspLnK53iuMJ1fJgLc= -golang.org/x/mobile v0.0.0-20240404231514-09dbf07665ed/go.mod h1:z041I2NhLjANgIfD0XbB2AmUZ8sLUcSgyLaSNGEP50M= +golang.org/x/mobile v0.0.0-20241016134751-7ff83004ec2c h1:zuNS/LWsEpPTLfrmBkis6Xofw3nieAqB4hYLn8+uswk= +golang.org/x/mobile v0.0.0-20241016134751-7ff83004ec2c/go.mod h1:snk1Mn2ZpdKCt90JPEsDh4sL3ReK520U2t0d7RHBnSU= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1099,8 +1099,8 @@ golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/oauth2 v0.0.0-20170912212905-13449ad91cb2/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1163,8 +1163,8 @@ golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1188,8 +1188,8 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20170424234030-8be79e1e0910/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= @@ -1209,8 +1209,8 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.8-0.20211029000441-d6a9af8af023/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= -golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= +golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=