diff --git a/android/.project b/android/.project index 09702ebb4..69f510cf3 100644 --- a/android/.project +++ b/android/.project @@ -16,12 +16,12 @@ - 1620924294143 + 1732230225439 30 org.eclipse.core.resources.regexFilterMatcher - node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ diff --git a/assets/locales/fr-ca.po b/assets/locales/fr-ca.po index 98b6269f6..6dc9a13f3 100644 --- a/assets/locales/fr-ca.po +++ b/assets/locales/fr-ca.po @@ -467,7 +467,7 @@ msgid "pro_plan" msgstr "Abonnement Pro" msgid "one_month_plan" -msgstr "" +msgstr "Abonnement d’un mois" msgid "one_year_plan" msgstr "Abonnement d’un an" @@ -1823,7 +1823,7 @@ msgid "recovery_not_found" msgstr "L’appareil n’est pas associé à cet utilisateur" msgid "device_added" -msgstr "" +msgstr "L’appareil a été ajouté" msgid "device_added_message" msgstr "" @@ -1896,16 +1896,16 @@ msgid "follow_us" msgstr "Suivez-nous " msgid "follow_us_telegram" -msgstr "" +msgstr "Suivez-nous sur Telegram" msgid "follow_us_instagram" -msgstr "" +msgstr "Suivez-nous sur Instagram" msgid "follow_us_facebook" -msgstr "" +msgstr "Suivez-nous sur Facebook" msgid "follow_us_x" -msgstr "" +msgstr "Suivez-nous sur X" msgid "report_issue_error" msgstr "Nous rencontrons des difficultés techniques. Réessayez plus tard. " @@ -1955,52 +1955,59 @@ msgid "file_size_limit_description" msgstr "Les tailles des pièces jointes doivent être inférieures à 100 Mo" msgid "lantern_pro_one_year" -msgstr "" +msgstr "Lantern Pro : abonnement d’un an" msgid "lantern_pro_two_year" -msgstr "" +msgstr "Lantern Pro : abonnement de 2 ans" msgid "lantern_pro_one_month" -msgstr "" +msgstr "Lantern Pro : abonnement d’un mois" msgid "non_renewing_subscription" -msgstr "" +msgstr "Abonnement non renouvelable" msgid "restore_purchase" -msgstr "" +msgstr "Rétablir l’achat" msgid "skip_for_now" -msgstr "" +msgstr "Ignorer pour l’instant" msgid "purchase_not_found" -msgstr "" +msgstr "Aucun achat n’a été trouvé avec l’adresse courriel indiquée" msgid "no_previous_purchase" -msgstr "" +msgstr "Aucun achat antérieur n’a été trouvé" msgid "warning_restore_purchase" msgstr "" +"Afin de rétablir votre achat, saisissez l’adresse courriel que vous avez " +"saisie lors de la commande." msgid "purchase_restored" -msgstr "" +msgstr "L’achat a été rétabli" msgid "purchase_restored_message" -msgstr "" +msgstr "Votre achat a été rétabli" msgid "purchase_restored_error" msgstr "" +"Le rétablissement de l’achat n’a pas fonctionné. Vérifiez avoir saisi la " +"bonne adresse courriel et le bon code." msgid "err_while_sending_code" -msgstr "" +msgstr "Une erreur est survenue lors de l’envoi du code. Réessayez" msgid "email_hint" -msgstr "" +msgstr "*L’adresse courriel est facultative, mais souhaitable" msgid "email_hint_pro" msgstr "" +"Ajoutez une adresse courriel pour accéder à vos achats sur tous vos " +"appareils. Sans adresse courriel, les achats sont liés à cet appareil et " +"pourraient être perdus." msgid "continue_without_email" -msgstr "" +msgstr "Poursuivre sans adresse courriel" msgid "no_new_update" msgstr "Lantern est à jour." @@ -2010,25 +2017,27 @@ msgstr "" "Une erreur est survenue lors de l’application de votre code de parrainage." msgid "proxy_error" -msgstr "" +msgstr "Le démarrage de Lantern présente un problème. Réessayez plus tard." msgid "proxy_settings" -msgstr "" +msgstr "Paramètres du mandataire" msgid "http_proxy" -msgstr "" +msgstr "Mandataire HTTPS" msgid "socks_proxy" -msgstr "" +msgstr "Mandataire SOCKS" msgid "http_proxy_copied" -msgstr "" +msgstr "Le mandataire HTTP a été copié" msgid "socks_proxy_copied" -msgstr "" +msgstr "Le mandataire SOCK a été copié" msgid "proxy_everything" -msgstr "" +msgstr "Tout relayer par le mandataire" msgid "errorSubmittingToGooglePlay" msgstr "" +"Il nous est actuellement impossible de traiter votre demande pour Google " +"Play. Réessayez plus tard." diff --git a/assets/locales/fr-fr.po b/assets/locales/fr-fr.po index 41e795077..adb49847f 100644 --- a/assets/locales/fr-fr.po +++ b/assets/locales/fr-fr.po @@ -470,7 +470,7 @@ msgid "pro_plan" msgstr "Abonnement Pro" msgid "one_month_plan" -msgstr "" +msgstr "Abonnement d’un mois" msgid "one_year_plan" msgstr "Abonnement d’un an" @@ -1826,7 +1826,7 @@ msgid "recovery_not_found" msgstr "L’appareil n’est pas associé à cet utilisateur" msgid "device_added" -msgstr "" +msgstr "L’appareil a été ajouté" msgid "device_added_message" msgstr "" @@ -1899,16 +1899,16 @@ msgid "follow_us" msgstr "Suivez-nous " msgid "follow_us_telegram" -msgstr "" +msgstr "Suivez-nous sur Telegram" msgid "follow_us_instagram" -msgstr "" +msgstr "Suivez-nous sur Instagram" msgid "follow_us_facebook" -msgstr "" +msgstr "Suivez-nous sur Facebook" msgid "follow_us_x" -msgstr "" +msgstr "Suivez-nous sur X" msgid "report_issue_error" msgstr "Nous rencontrons des difficultés techniques. Réessayez plus tard. " @@ -1958,52 +1958,59 @@ msgid "file_size_limit_description" msgstr "Les tailles des pièces jointes doivent être inférieures à 100 Mo" msgid "lantern_pro_one_year" -msgstr "" +msgstr "Lantern Pro : abonnement d’un an" msgid "lantern_pro_two_year" -msgstr "" +msgstr "Lantern Pro : abonnement de 2 ans" msgid "lantern_pro_one_month" -msgstr "" +msgstr "Lantern Pro : abonnement d’un mois" msgid "non_renewing_subscription" -msgstr "" +msgstr "Abonnement non renouvelable" msgid "restore_purchase" -msgstr "" +msgstr "Rétablir l’achat" msgid "skip_for_now" -msgstr "" +msgstr "Ignorer pour l’instant" msgid "purchase_not_found" -msgstr "" +msgstr "Aucun achat n’a été trouvé avec l’adresse courriel indiquée" msgid "no_previous_purchase" -msgstr "" +msgstr "Aucun achat antérieur n’a été trouvé" msgid "warning_restore_purchase" msgstr "" +"Afin de rétablir votre achat, saisissez l’adresse courriel que vous avez " +"saisie lors de la commande." msgid "purchase_restored" -msgstr "" +msgstr "L’achat a été rétabli" msgid "purchase_restored_message" -msgstr "" +msgstr "Votre achat a été rétabli" msgid "purchase_restored_error" msgstr "" +"Le rétablissement de l’achat n’a pas fonctionné. Vérifiez avoir saisi la " +"bonne adresse courriel et le bon code." msgid "err_while_sending_code" -msgstr "" +msgstr "Une erreur est survenue lors de l’envoi du code. Réessayez" msgid "email_hint" -msgstr "" +msgstr "*L’adresse courriel est facultative, mais souhaitable" msgid "email_hint_pro" msgstr "" +"Ajoutez une adresse courriel pour accéder à vos achats sur tous vos " +"appareils. Sans adresse courriel, les achats sont liés à cet appareil et " +"pourraient être perdus." msgid "continue_without_email" -msgstr "" +msgstr "Poursuivre sans adresse courriel" msgid "no_new_update" msgstr "Lantern est à jour." @@ -2013,25 +2020,27 @@ msgstr "" "Une erreur est survenue lors de l’application de votre code de parrainage." msgid "proxy_error" -msgstr "" +msgstr "Le démarrage de Lantern présente un problème. Réessayez plus tard." msgid "proxy_settings" -msgstr "" +msgstr "Paramètres du mandataire" msgid "http_proxy" -msgstr "" +msgstr "Mandataire HTTPS" msgid "socks_proxy" -msgstr "" +msgstr "Mandataire SOCKS" msgid "http_proxy_copied" -msgstr "" +msgstr "Le mandataire HTTP a été copié" msgid "socks_proxy_copied" -msgstr "" +msgstr "Le mandataire SOCK a été copié" msgid "proxy_everything" -msgstr "" +msgstr "Tout relayer par le mandataire" msgid "errorSubmittingToGooglePlay" msgstr "" +"Il nous est actuellement impossible de traiter votre demande pour Google " +"Play. Réessayez plus tard." diff --git a/desktop/app/app.go b/desktop/app/app.go index 21a1a176c..f276bd73d 100644 --- a/desktop/app/app.go +++ b/desktop/app/app.go @@ -86,7 +86,6 @@ type App struct { ws ws.UIChannel cachedUserData sync.Map - plansCache sync.Map onUserData []func(current *protos.User, new *protos.User) @@ -381,7 +380,7 @@ func (app *App) OnStatsChange(fn func(stats.Stats)) { func (app *App) afterStart(cl *flashlightClient.Client) { ctx := context.Background() go app.fetchOrCreateUser(ctx) - go app.GetPaymentMethods(ctx) + go app.proClient.DesktopPaymentMethods(ctx) go app.fetchDeviceLinkingCode(ctx) app.OnSettingChange(settings.SNSystemProxy, func(val interface{}) { @@ -567,17 +566,18 @@ func (app *App) IsPro() bool { return isPro } -func (app *App) fetchOrCreateUser(ctx context.Context) (*protos.User, error) { - settings := app.Settings() - lang := settings.GetLanguage() +func (app *App) fetchOrCreateUser(ctx context.Context) { + ss := app.Settings() + lang := ss.GetLanguage() if lang == "" { // set default language - settings.SetLanguage("en_us") + ss.SetLanguage("en_us") } - if userID := settings.GetUserID(); userID == 0 { - return app.CreateUser(ctx) + if userID := ss.GetUserID(); userID == 0 { + ss.SetUserFirstVisit(true) + app.proClient.RetryCreateUser(ctx, app, 5*time.Minute) } else { - return app.UserData(ctx) + app.proClient.UpdateUserData(ctx, app) } } @@ -597,145 +597,6 @@ func (app *App) fetchDeviceLinkingCode(ctx context.Context) (string, error) { return resp.Code, nil } -// CreateUser is used when Lantern is run for the first time and creates a new user with the pro server -func (app *App) CreateUser(ctx context.Context) (*protos.User, error) { - log.Debug("New user, calling user create") - settings := app.Settings() - settings.SetUserFirstVisit(true) - resp, err := app.proClient.UserCreate(ctx) - if err != nil { - return nil, errors.New("Could not create new Pro user: %v", err) - } - user := resp.User - log.Debugf("DEBUG: User created: %v", user) - if resp.BaseResponse != nil && resp.BaseResponse.Error != "" { - return nil, errors.New("Could not create new Pro user: %v", err) - } - app.SetUserData(ctx, user.UserId, user) - settings.SetReferralCode(user.Referral) - settings.SetUserIDAndToken(user.UserId, user.Token) - go app.UserData(ctx) - return resp.User, nil -} - -// Plans returns the plans available to a user -func (app *App) Plans(ctx context.Context) ([]protos.Plan, error) { - if v, ok := app.plansCache.Load("plans"); ok { - resp := v.([]protos.Plan) - log.Debugf("Returning plans from cache %s", v) - return resp, nil - } - resp, err := app.FetchPaymentMethods(ctx) - if err != nil { - return nil, err - } - return resp.Plans, nil -} - -// GetPaymentMethods returns the plans and payment from cache if available -// if not then call FetchPaymentMethods -func (app *App) GetPaymentMethods(ctx context.Context) ([]protos.PaymentMethod, error) { - if v, ok := app.plansCache.Load("paymentMethods"); ok { - resp := v.([]protos.PaymentMethod) - log.Debugf("Returning payment methods from cache %s", v) - return resp, nil - } - resp, err := app.FetchPaymentMethods(ctx) - if err != nil { - return nil, err - } - desktopProviders, ok := resp.Providers["desktop"] - if !ok { - return nil, errors.New("No desktop payment providers found") - } - return desktopProviders, nil -} - -// FetchPaymentMethods returns the plans and payment plans available to a user -func (app *App) FetchPaymentMethods(ctx context.Context) (*proclient.PaymentMethodsResponse, error) { - resp, err := app.proClient.PaymentMethodsV4(context.Background()) - if err != nil { - return nil, errors.New("Could not get payment methods: %v", err) - } - desktopPaymentMethods, ok := resp.Providers["desktop"] - if !ok { - return nil, errors.New("No desktop payment providers found") - } - for i := range desktopPaymentMethods { - paymentMethod := &desktopPaymentMethods[i] - for j, provider := range paymentMethod.Providers { - if resp.Logo[provider.Name] != nil { - logos := resp.Logo[provider.Name].([]interface{}) - for _, logo := range logos { - paymentMethod.Providers[j].LogoUrls = append(paymentMethod.Providers[j].LogoUrls, logo.(string)) - } - } - } - } - //clear previous store cache - app.plansCache.Delete("plans") - app.plansCache.Delete("paymentMethods") - log.Debugf("DEBUG: Payment methods plans: %+v", resp.Plans) - log.Debugf("DEBUG: Payment methods providers: %+v", desktopPaymentMethods) - app.plansCache.Store("plans", resp.Plans) - app.plansCache.Store("paymentMethods", desktopPaymentMethods) - app.sendConfigOptions() - return resp, nil -} - -// UserData looks up user data that is associated with the given UserConfig -func (app *App) UserData(ctx context.Context) (*protos.User, error) { - log.Debug("Refreshing user data") - resp, err := app.proClient.UserData(context.Background()) - if err != nil { - return nil, errors.New("error fetching user data: %v", err) - } else if resp.User == nil { - return nil, errors.New("error fetching user data") - } - userDetail := resp.User - settings := app.Settings() - - setProUser := func(isPro bool) { - app.Settings().SetProUser(isPro) - } - - currentDevice := app.Settings().GetDeviceID() - - // Check if device id is connect to same device if not create new user - // this is for the case when user removed device from other device - deviceFound := false - if userDetail.Devices != nil { - for _, device := range userDetail.Devices { - if device.Id == currentDevice { - deviceFound = true - break - } - } - } - - /// Check if user has installed app first time - firstTime := settings.GetUserFirstVisit() - log.Debugf("First time visit %v", firstTime) - if userDetail.UserLevel == "pro" && firstTime { - log.Debugf("User is pro and first time") - setProUser(true) - } else if userDetail.UserLevel == "pro" && !firstTime && deviceFound { - log.Debugf("User is pro and not first time") - setProUser(true) - } else { - log.Debugf("User is not pro") - setProUser(false) - } - settings.SetUserIDAndToken(userDetail.UserId, userDetail.Token) - settings.SetExpiration(userDetail.Expiration) - settings.SetReferralCode(resp.User.Referral) - log.Debugf("User caching successful: %+v", userDetail) - // Save data in userData cache - app.SetUserData(ctx, userDetail.UserId, userDetail) - app.SendUpdateUserDataToUI() - return resp.User, nil -} - func (app *App) devices() protos.Devices { user, found := app.GetUserData(app.Settings().GetUserID()) @@ -807,3 +668,38 @@ func (app *App) ProClient() proclient.ProClient { defer app.mu.RUnlock() return app.proClient } + +// Client session methods +func (app *App) FetchUserData() error { + go app.proClient.UserData(context.Background()) + go app.proClient.FetchPaymentMethodsAndCache(context.Background()) + return nil +} + +func (app *App) GetDeviceID() (string, error) { + return app.Settings().GetDeviceID(), nil +} + +func (app *App) GetUserFirstVisit() (bool, error) { + return app.Settings().GetUserFirstVisit(), nil +} + +func (app *App) SetUserIDAndToken(id int64, token string) error { + app.Settings().SetUserIDAndToken(id, token) + return nil +} + +func (app *App) SetProUser(pro bool) error { + app.Settings().SetProUser(pro) + return nil +} + +func (app *App) SetReferralCode(referral string) error { + app.Settings().SetReferralCode(referral) + return nil +} + +func (app *App) SetExpiration(exp int64) error { + app.Settings().SetExpiration(exp) + return nil +} diff --git a/desktop/app/config.go b/desktop/app/config.go index 9e2363ae4..dbeb6c1b8 100644 --- a/desktop/app/config.go +++ b/desktop/app/config.go @@ -84,8 +84,8 @@ func (app *App) sendConfigOptions() { return authEnabled } ctx := context.Background() - plans, _ := app.Plans(ctx) - paymentMethods, _ := app.GetPaymentMethods(ctx) + plans, _ := app.proClient.Plans(ctx) + paymentMethods, _ := app.proClient.DesktopPaymentMethods(ctx) devices, _ := json.Marshal(app.devices()) log.Debugf("DEBUG: Devices: %s", string(devices)) log.Debugf("Expiration date: %s", app.settings.GetExpirationDate()) diff --git a/desktop/app/pro.go b/desktop/app/pro.go index 418391892..4050df4fe 100644 --- a/desktop/app/pro.go +++ b/desktop/app/pro.go @@ -4,9 +4,7 @@ import ( "context" "encoding/json" "reflect" - "time" - "github.com/getlantern/errors" "github.com/getlantern/flashlight/v7/common" "github.com/getlantern/lantern-client/desktop/ws" "github.com/getlantern/lantern-client/internalsdk/pro" @@ -14,7 +12,7 @@ import ( ) // onProStatusChange allows registering an event handler to learn when the -// user's pro status or "yinbi enabled" status has changed. +// user's pro status changes func (app *App) onProStatusChange(cb func(isPro bool)) { app.setOnUserData(func(current *protos.User, new *protos.User) { if current == nil || isActive(current) != isActive(new) { @@ -109,53 +107,16 @@ func (app *App) IsProUserFast(uc common.UserConfig) (isPro bool, statusKnown boo // It loops forever in 10 seconds interval until the user is fetched or // created, as it's fundamental for the UI to work. func (app *App) servePro(channel ws.UIChannel) error { - chFetch := make(chan bool) - ctx := context.Background() - go func() { - fetchOrCreate := func() error { - userID := app.settings.GetUserID() - if userID == 0 { - resp, err := app.proClient.UserCreate(ctx) - if err != nil { - return errors.New("Could not create new Pro user: %v", err) - } - app.settings.SetUserIDAndToken(resp.User.UserId, resp.User.Token) - } else { - _, err := app.proClient.UserData(ctx) - if err != nil { - return errors.New("Could not get user data for %v: %v", userID, err) - } - } - return nil - } - - retry := time.NewTimer(0) - retryOnFail := func(drainChannel bool) { - if err := fetchOrCreate(); err != nil { - if drainChannel && !retry.Stop() { - <-retry.C - } - retry.Reset(10 * time.Second) - } - } - for { - select { - case <-chFetch: - retryOnFail(true) - case <-retry.C: - retryOnFail(false) - } - } - }() - service, err := channel.Register("pro", nil) if err != nil { return err } app.setOnUserData(func(current *protos.User, new *protos.User) { - b, _ := json.Marshal(new) - log.Debugf("Sending updated user data to all clients: %s", string(b)) - service.Out <- new + if new != nil { + b, _ := json.Marshal(new) + log.Debugf("Sending updated user data to all clients: %s", string(b)) + service.Out <- new + } }) return err } diff --git a/desktop/auth.go b/desktop/auth.go index 5a6609e6b..50680f1d3 100644 --- a/desktop/auth.go +++ b/desktop/auth.go @@ -70,7 +70,7 @@ func signup(email *C.char, password *C.char) *C.char { saveUserSalt(salt) setting.SetEmailAddress(C.GoString(email)) a.SetUserLoggedIn(true) - a.FetchPaymentMethods(context.Background()) + a.ProClient().FetchPaymentMethodsAndCache(context.Background()) return C.CString("true") } @@ -129,7 +129,7 @@ func logout() *C.char { clearLocalUserData() // Create new user - if _, err := a.CreateUser(ctx); err != nil { + if _, err := a.ProClient().UserCreate(ctx); err != nil { return sendError(err) } return C.CString("true") @@ -253,7 +253,7 @@ func deleteAccount(password *C.char) *C.char { A: A.Bytes(), } log.Debugf("Delete Account request email %v A %v", lowerCaseEmail, A.Bytes()) - srpB, err := a.AuthClient().LoginPrepare(context.Background(), prepareRequestBody) + srpB, err := a.AuthClient().LoginPrepare(ctx, prepareRequestBody) if err != nil { return sendError(err) } @@ -299,7 +299,7 @@ func deleteAccount(password *C.char) *C.char { if err != nil { return sendError(err) } - log.Debugf("Account Delted response %v", isAccountDeleted) + log.Debugf("Account deleted response %v", isAccountDeleted) if !isAccountDeleted { return sendError(log.Errorf("user_not_found error while deleting account %v", err)) @@ -310,8 +310,7 @@ func deleteAccount(password *C.char) *C.char { // Set user id and token to nil a.Settings().SetUserIDAndToken(0, "") // Create new user - // Create new user - if _, err := a.CreateUser(ctx); err != nil { + if _, err := a.ProClient().UserCreate(ctx); err != nil { return sendError(err) } return C.CString("true") diff --git a/desktop/lib.go b/desktop/lib.go index 40d8d9be9..5ce5de3e1 100644 --- a/desktop/lib.go +++ b/desktop/lib.go @@ -129,19 +129,23 @@ func setProxyAll(value *C.char) { // //export hasPlanUpdatedOrBuy func hasPlanUpdatedOrBuy() *C.char { + ctx := context.Background() + proClient := a.ProClient() + go proClient.PollUserData(ctx, a, 10*time.Minute, proClient) //Get the cached user data log.Debugf("DEBUG: Checking if user has updated plan or bought new plan") cacheUserData, isOldFound := cachedUserData() //Get latest user data - resp, err := a.ProClient().UserData(context.Background()) + resp, err := a.ProClient().UserData(ctx) if err != nil { return sendError(err) } if isOldFound { - if cacheUserData.Expiration < resp.User.Expiration { + user := resp.User + if cacheUserData.Expiration < user.Expiration { // New data has a later expiration // if foud then update the cache - a.Settings().SetExpiration(resp.User.Expiration) + a.Settings().SetExpiration(user.Expiration) return C.CString(string("true")) } } @@ -249,8 +253,6 @@ func testProviderRequest(email *C.char, paymentProvider *C.char, plan *C.char) * if err != nil { return sendError(err) } - //a.SetProUser(true) - go a.UserData(ctx) return C.CString("true") } @@ -364,7 +366,8 @@ func deviceLinkingCode() *C.char { //export paymentRedirect func paymentRedirect(planID, currency, provider, email, deviceName *C.char) *C.char { country := a.Settings().GetCountry() - resp, err := a.ProClient().PaymentRedirect(context.Background(), &protos.PaymentRedirectRequest{ + ctx := context.Background() + resp, err := a.ProClient().PaymentRedirect(ctx, &protos.PaymentRedirectRequest{ Plan: C.GoString(planID), Provider: C.GoString(provider), Currency: strings.ToUpper(C.GoString(currency)), @@ -375,6 +378,7 @@ func paymentRedirect(planID, currency, provider, email, deviceName *C.char) *C.c if err != nil { return sendError(err) } + return sendJson(resp) } diff --git a/desktop/settings/settings.go b/desktop/settings/settings.go index 211da14d8..ffb29c6e6 100644 --- a/desktop/settings/settings.go +++ b/desktop/settings/settings.go @@ -508,6 +508,11 @@ func (s *Settings) GetLanguage() string { return s.getString(SNLanguage) } +// Locale returns the user language +func (s *Settings) Locale() string { + return s.getString(SNLanguage) +} + // SetReferralCode sets the user referral code func (s *Settings) SetReferralCode(referralCode string) { s.setVal(SNReferralCode, referralCode) diff --git a/internalsdk/android.go b/internalsdk/android.go index 8d405a03c..04c294be9 100644 --- a/internalsdk/android.go +++ b/internalsdk/android.go @@ -78,10 +78,17 @@ type AdProvider interface { // thrown from the Java code. If a method interface doesn't include an error, exceptions on the // Java side immediately result in a panic from which Go cannot recover. type Session interface { - ClientSession + GetDeviceID() (string, error) + GetUserID() (int64, error) + GetToken() (string, error) + Locale() (string, error) + GetUserFirstVisit() (bool, error) GetAppName() string SetCountry(string) error + SetExpiration(int64) error SetIP(string) error + SetReferralCode(string) error + SetProUser(bool) error UpdateAdSettings(AdSettings) error UpdateStats(serverCity string, serverCountry string, serverCountryCode string, p3 int, p4 int, hasSucceedingProxy bool) error SetStaging(bool) error @@ -105,7 +112,7 @@ type Session interface { SetShowGoogleAds(bool) SetHasConfigFetched(bool) SetHasProxyFetched(bool) - SetUserIdAndToken(int64, string) error + SetUserIDAndToken(int64, string) error SetOnSuccess(bool) ChatEnable() bool // workaround for lack of any sequence types in gomobile bind... ;_; @@ -117,9 +124,11 @@ type Session interface { // PanickingSession wraps the Session interface but panics instead of returning errors type PanickingSession interface { common.AuthConfig + GetUserFirstVisit() bool SetCountry(string) UpdateAdSettings(AdSettings) UpdateStats(string, string, string, int, int, bool) + SetExpiration(int64) SetStaging(bool) BandwidthUpdate(int, int, int, int) Locale() string @@ -145,7 +154,9 @@ type PanickingSession interface { SerializedInternalHeaders() string SetHasConfigFetched(bool) SetHasProxyFetched(bool) - SetUserIdAndToken(int64, string) + SetProUser(bool) + SetReferralCode(string) + SetUserIDAndToken(int64, string) SetOnSuccess(bool) ChatEnable() bool @@ -183,10 +194,28 @@ func (s *panickingSessionImpl) GetToken() string { return result } +func (s *panickingSessionImpl) GetUserFirstVisit() bool { + result, err := s.wrapped.GetUserFirstVisit() + panicIfNecessary(err) + return result +} + func (s *panickingSessionImpl) SetCountry(country string) { panicIfNecessary(s.wrapped.SetCountry(country)) } +func (s *panickingSessionImpl) SetExpiration(expiration int64) { + panicIfNecessary(s.wrapped.SetExpiration(expiration)) +} + +func (s *panickingSessionImpl) SetProUser(proUser bool) { + panicIfNecessary(s.wrapped.SetProUser(proUser)) +} + +func (s *panickingSessionImpl) SetReferralCode(referralCode string) { + panicIfNecessary(s.wrapped.SetReferralCode(referralCode)) +} + func (s *panickingSessionImpl) SetIP(ipAddress string) { panicIfNecessary(s.wrapped.SetIP(ipAddress)) } @@ -300,8 +329,8 @@ func (s *panickingSessionImpl) SetShowGoogleAds(enabled bool) { s.wrapped.SetShowGoogleAds(enabled) } -func (s *panickingSessionImpl) SetUserIdAndToken(userID int64, token string) { - err := s.wrapped.SetUserIdAndToken(userID, token) +func (s *panickingSessionImpl) SetUserIDAndToken(userID int64, token string) { + err := s.wrapped.SetUserIDAndToken(userID, token) panicIfNecessary(err) } @@ -538,7 +567,7 @@ func run(configDir, locale string, settings Settings, wrappedSession Session) { config.ForceCountry(forcedCountryCode) } - userConfig := newUserConfig(session) + userConfig := &userConfig{session: session} globalConfigChanged := make(chan interface{}) geoRefreshed := geolookup.OnRefresh() @@ -680,14 +709,7 @@ func geoLookup(session PanickingSession) { } func afterStart(wrappedSession Session, session PanickingSession) { - - if session.GetUserID() == 0 { - ctx := context.Background() - go retryCreateUser(ctx, wrappedSession) - } - bandwidthUpdates(session) - go func() { if <-geolookup.OnRefresh() { geoLookup(session) diff --git a/internalsdk/android_test.go b/internalsdk/android_test.go index 59526d2b9..9f576b536 100644 --- a/internalsdk/android_test.go +++ b/internalsdk/android_test.go @@ -64,6 +64,7 @@ func (c testSession) UpdateStats(string, string, string, int, int, bool) error { func (c testSession) UpdateAdSettings(AdSettings) error { return nil } func (c testSession) GetAppName() string { return "lantern" } +func (c testSession) GetUserFirstVisit() (bool, error) { return false, nil } func (c testSession) AppVersion() (string, error) { return "6.9.0", nil } func (c testSession) Code() (string, error) { return "1", nil } func (c testSession) Currency() (string, error) { return "usd", nil } @@ -73,9 +74,12 @@ func (c testSession) GetCountryCode() (string, error) { retur func (c testSession) IsStoreVersion() (bool, error) { return false, nil } func (c testSession) Provider() (string, error) { return "stripe", nil } func (c testSession) SetChatEnabled(enabled bool) {} -func (c testSession) SetUserIdAndToken(userId int64, token string) error { return nil } +func (c testSession) SetUserIDAndToken(userId int64, token string) error { return nil } func (c testSession) SetAuthEnabled(enabled bool) {} func (c testSession) SetMatomoEnabled(bool) {} +func (c testSession) SetExpiration(expiration int64) error { return nil } +func (c testSession) SetProUser(proUser bool) error { return nil } +func (c testSession) SetReferralCode(referralCode string) error { return nil } func (c testSession) IsPlayVersion() (bool, error) { return false, nil } func (c testSession) SetShowGoogleAds(enabled bool) {} func (c testSession) SetHasConfigFetched(enabled bool) {} diff --git a/internalsdk/issue.go b/internalsdk/issue.go index f09e844a7..f162deb03 100644 --- a/internalsdk/issue.go +++ b/internalsdk/issue.go @@ -35,7 +35,7 @@ func SendIssueReport( return err } return issue.SendReport( - newUserConfig(&panickingSessionImpl{session}), + &userConfig{&panickingSessionImpl{session}}, issueTypeInt, description, subscriptionLevel, diff --git a/internalsdk/mocks/client_session.go b/internalsdk/mocks/client_session.go new file mode 100644 index 000000000..ae7dcee86 --- /dev/null +++ b/internalsdk/mocks/client_session.go @@ -0,0 +1,134 @@ +// Code generated by mockery v2.46.3. DO NOT EDIT. + +package mocks + +import mock "github.com/stretchr/testify/mock" + +// ClientSession is an autogenerated mock type for the ClientSession type +type ClientSession struct { + mock.Mock +} + +// GetDeviceID provides a mock function with given fields: +func (_m *ClientSession) GetDeviceID() string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GetDeviceID") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// GetToken provides a mock function with given fields: +func (_m *ClientSession) GetToken() string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GetToken") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// GetUserFirstVisit provides a mock function with given fields: +func (_m *ClientSession) GetUserFirstVisit() bool { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GetUserFirstVisit") + } + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// GetUserID provides a mock function with given fields: +func (_m *ClientSession) GetUserID() int64 { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GetUserID") + } + + var r0 int64 + if rf, ok := ret.Get(0).(func() int64); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(int64) + } + + return r0 +} + +// Locale provides a mock function with given fields: +func (_m *ClientSession) Locale() string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Locale") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// SetExpiration provides a mock function with given fields: _a0 +func (_m *ClientSession) SetExpiration(_a0 int64) { + _m.Called(_a0) +} + +// SetProUser provides a mock function with given fields: _a0 +func (_m *ClientSession) SetProUser(_a0 bool) { + _m.Called(_a0) +} + +// SetReferralCode provides a mock function with given fields: _a0 +func (_m *ClientSession) SetReferralCode(_a0 string) { + _m.Called(_a0) +} + +// SetUserIDAndToken provides a mock function with given fields: _a0, _a1 +func (_m *ClientSession) SetUserIDAndToken(_a0 int64, _a1 string) { + _m.Called(_a0, _a1) +} + +// NewClientSession creates a new instance of ClientSession. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewClientSession(t interface { + mock.TestingT + Cleanup(func()) +}) *ClientSession { + mock := &ClientSession{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internalsdk/mocks/pro_client.go b/internalsdk/mocks/pro_client.go index 1c29b2185..deae3bbf4 100644 --- a/internalsdk/mocks/pro_client.go +++ b/internalsdk/mocks/pro_client.go @@ -11,6 +11,8 @@ import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protos "github.com/getlantern/lantern-client/internalsdk/protos" + + time "time" ) // ProClient is an autogenerated mock type for the ProClient type @@ -18,6 +20,14 @@ type ProClient struct { mock.Mock } +type ProClient_Expecter struct { + mock *mock.Mock +} + +func (_m *ProClient) EXPECT() *ProClient_Expecter { + return &ProClient_Expecter{mock: &_m.Mock} +} + // DeviceAdd provides a mock function with given fields: ctx, deviceName func (_m *ProClient) DeviceAdd(ctx context.Context, deviceName string) (bool, error) { ret := _m.Called(ctx, deviceName) @@ -46,6 +56,35 @@ func (_m *ProClient) DeviceAdd(ctx context.Context, deviceName string) (bool, er return r0, r1 } +// ProClient_DeviceAdd_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeviceAdd' +type ProClient_DeviceAdd_Call struct { + *mock.Call +} + +// DeviceAdd is a helper method to define mock.On call +// - ctx context.Context +// - deviceName string +func (_e *ProClient_Expecter) DeviceAdd(ctx interface{}, deviceName interface{}) *ProClient_DeviceAdd_Call { + return &ProClient_DeviceAdd_Call{Call: _e.mock.On("DeviceAdd", ctx, deviceName)} +} + +func (_c *ProClient_DeviceAdd_Call) Run(run func(ctx context.Context, deviceName string)) *ProClient_DeviceAdd_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *ProClient_DeviceAdd_Call) Return(_a0 bool, _a1 error) *ProClient_DeviceAdd_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ProClient_DeviceAdd_Call) RunAndReturn(run func(context.Context, string) (bool, error)) *ProClient_DeviceAdd_Call { + _c.Call.Return(run) + return _c +} + // DeviceRemove provides a mock function with given fields: ctx, deviceId func (_m *ProClient) DeviceRemove(ctx context.Context, deviceId string) (*pro.LinkResponse, error) { ret := _m.Called(ctx, deviceId) @@ -76,6 +115,35 @@ func (_m *ProClient) DeviceRemove(ctx context.Context, deviceId string) (*pro.Li return r0, r1 } +// ProClient_DeviceRemove_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeviceRemove' +type ProClient_DeviceRemove_Call struct { + *mock.Call +} + +// DeviceRemove is a helper method to define mock.On call +// - ctx context.Context +// - deviceId string +func (_e *ProClient_Expecter) DeviceRemove(ctx interface{}, deviceId interface{}) *ProClient_DeviceRemove_Call { + return &ProClient_DeviceRemove_Call{Call: _e.mock.On("DeviceRemove", ctx, deviceId)} +} + +func (_c *ProClient_DeviceRemove_Call) Run(run func(ctx context.Context, deviceId string)) *ProClient_DeviceRemove_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *ProClient_DeviceRemove_Call) Return(_a0 *pro.LinkResponse, _a1 error) *ProClient_DeviceRemove_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ProClient_DeviceRemove_Call) RunAndReturn(run func(context.Context, string) (*pro.LinkResponse, error)) *ProClient_DeviceRemove_Call { + _c.Call.Return(run) + return _c +} + // EmailExists provides a mock function with given fields: ctx, email func (_m *ProClient) EmailExists(ctx context.Context, email string) (*protos.BaseResponse, error) { ret := _m.Called(ctx, email) @@ -106,6 +174,35 @@ func (_m *ProClient) EmailExists(ctx context.Context, email string) (*protos.Bas return r0, r1 } +// ProClient_EmailExists_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EmailExists' +type ProClient_EmailExists_Call struct { + *mock.Call +} + +// EmailExists is a helper method to define mock.On call +// - ctx context.Context +// - email string +func (_e *ProClient_Expecter) EmailExists(ctx interface{}, email interface{}) *ProClient_EmailExists_Call { + return &ProClient_EmailExists_Call{Call: _e.mock.On("EmailExists", ctx, email)} +} + +func (_c *ProClient_EmailExists_Call) Run(run func(ctx context.Context, email string)) *ProClient_EmailExists_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *ProClient_EmailExists_Call) Return(_a0 *protos.BaseResponse, _a1 error) *ProClient_EmailExists_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ProClient_EmailExists_Call) RunAndReturn(run func(context.Context, string) (*protos.BaseResponse, error)) *ProClient_EmailExists_Call { + _c.Call.Return(run) + return _c +} + // EmailRequest provides a mock function with given fields: ctx, email func (_m *ProClient) EmailRequest(ctx context.Context, email string) (*pro.OkResponse, error) { ret := _m.Called(ctx, email) @@ -136,6 +233,35 @@ func (_m *ProClient) EmailRequest(ctx context.Context, email string) (*pro.OkRes return r0, r1 } +// ProClient_EmailRequest_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EmailRequest' +type ProClient_EmailRequest_Call struct { + *mock.Call +} + +// EmailRequest is a helper method to define mock.On call +// - ctx context.Context +// - email string +func (_e *ProClient_Expecter) EmailRequest(ctx interface{}, email interface{}) *ProClient_EmailRequest_Call { + return &ProClient_EmailRequest_Call{Call: _e.mock.On("EmailRequest", ctx, email)} +} + +func (_c *ProClient_EmailRequest_Call) Run(run func(ctx context.Context, email string)) *ProClient_EmailRequest_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *ProClient_EmailRequest_Call) Return(_a0 *pro.OkResponse, _a1 error) *ProClient_EmailRequest_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ProClient_EmailRequest_Call) RunAndReturn(run func(context.Context, string) (*pro.OkResponse, error)) *ProClient_EmailRequest_Call { + _c.Call.Return(run) + return _c +} + // GetJSON provides a mock function with given fields: ctx, path, params, target func (_m *ProClient) GetJSON(ctx context.Context, path string, params any, target any) error { ret := _m.Called(ctx, path, params, target) @@ -154,6 +280,37 @@ func (_m *ProClient) GetJSON(ctx context.Context, path string, params any, targe return r0 } +// ProClient_GetJSON_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetJSON' +type ProClient_GetJSON_Call struct { + *mock.Call +} + +// GetJSON is a helper method to define mock.On call +// - ctx context.Context +// - path string +// - params any +// - target any +func (_e *ProClient_Expecter) GetJSON(ctx interface{}, path interface{}, params interface{}, target interface{}) *ProClient_GetJSON_Call { + return &ProClient_GetJSON_Call{Call: _e.mock.On("GetJSON", ctx, path, params, target)} +} + +func (_c *ProClient_GetJSON_Call) Run(run func(ctx context.Context, path string, params any, target any)) *ProClient_GetJSON_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(any), args[3].(any)) + }) + return _c +} + +func (_c *ProClient_GetJSON_Call) Return(_a0 error) *ProClient_GetJSON_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *ProClient_GetJSON_Call) RunAndReturn(run func(context.Context, string, any, any) error) *ProClient_GetJSON_Call { + _c.Call.Return(run) + return _c +} + // GetPROTOC provides a mock function with given fields: ctx, path, params, target func (_m *ProClient) GetPROTOC(ctx context.Context, path string, params any, target protoreflect.ProtoMessage) error { ret := _m.Called(ctx, path, params, target) @@ -172,6 +329,37 @@ func (_m *ProClient) GetPROTOC(ctx context.Context, path string, params any, tar return r0 } +// ProClient_GetPROTOC_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetPROTOC' +type ProClient_GetPROTOC_Call struct { + *mock.Call +} + +// GetPROTOC is a helper method to define mock.On call +// - ctx context.Context +// - path string +// - params any +// - target protoreflect.ProtoMessage +func (_e *ProClient_Expecter) GetPROTOC(ctx interface{}, path interface{}, params interface{}, target interface{}) *ProClient_GetPROTOC_Call { + return &ProClient_GetPROTOC_Call{Call: _e.mock.On("GetPROTOC", ctx, path, params, target)} +} + +func (_c *ProClient_GetPROTOC_Call) Run(run func(ctx context.Context, path string, params any, target protoreflect.ProtoMessage)) *ProClient_GetPROTOC_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(any), args[3].(protoreflect.ProtoMessage)) + }) + return _c +} + +func (_c *ProClient_GetPROTOC_Call) Return(_a0 error) *ProClient_GetPROTOC_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *ProClient_GetPROTOC_Call) RunAndReturn(run func(context.Context, string, any, protoreflect.ProtoMessage) error) *ProClient_GetPROTOC_Call { + _c.Call.Return(run) + return _c +} + // LinkCodeApprove provides a mock function with given fields: ctx, code func (_m *ProClient) LinkCodeApprove(ctx context.Context, code string) (*protos.BaseResponse, error) { ret := _m.Called(ctx, code) @@ -202,6 +390,35 @@ func (_m *ProClient) LinkCodeApprove(ctx context.Context, code string) (*protos. return r0, r1 } +// ProClient_LinkCodeApprove_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LinkCodeApprove' +type ProClient_LinkCodeApprove_Call struct { + *mock.Call +} + +// LinkCodeApprove is a helper method to define mock.On call +// - ctx context.Context +// - code string +func (_e *ProClient_Expecter) LinkCodeApprove(ctx interface{}, code interface{}) *ProClient_LinkCodeApprove_Call { + return &ProClient_LinkCodeApprove_Call{Call: _e.mock.On("LinkCodeApprove", ctx, code)} +} + +func (_c *ProClient_LinkCodeApprove_Call) Run(run func(ctx context.Context, code string)) *ProClient_LinkCodeApprove_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *ProClient_LinkCodeApprove_Call) Return(_a0 *protos.BaseResponse, _a1 error) *ProClient_LinkCodeApprove_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ProClient_LinkCodeApprove_Call) RunAndReturn(run func(context.Context, string) (*protos.BaseResponse, error)) *ProClient_LinkCodeApprove_Call { + _c.Call.Return(run) + return _c +} + // LinkCodeRedeem provides a mock function with given fields: ctx, deviceName, deviceCode func (_m *ProClient) LinkCodeRedeem(ctx context.Context, deviceName string, deviceCode string) (*pro.LinkCodeRedeemResponse, error) { ret := _m.Called(ctx, deviceName, deviceCode) @@ -232,6 +449,36 @@ func (_m *ProClient) LinkCodeRedeem(ctx context.Context, deviceName string, devi return r0, r1 } +// ProClient_LinkCodeRedeem_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LinkCodeRedeem' +type ProClient_LinkCodeRedeem_Call struct { + *mock.Call +} + +// LinkCodeRedeem is a helper method to define mock.On call +// - ctx context.Context +// - deviceName string +// - deviceCode string +func (_e *ProClient_Expecter) LinkCodeRedeem(ctx interface{}, deviceName interface{}, deviceCode interface{}) *ProClient_LinkCodeRedeem_Call { + return &ProClient_LinkCodeRedeem_Call{Call: _e.mock.On("LinkCodeRedeem", ctx, deviceName, deviceCode)} +} + +func (_c *ProClient_LinkCodeRedeem_Call) Run(run func(ctx context.Context, deviceName string, deviceCode string)) *ProClient_LinkCodeRedeem_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string)) + }) + return _c +} + +func (_c *ProClient_LinkCodeRedeem_Call) Return(_a0 *pro.LinkCodeRedeemResponse, _a1 error) *ProClient_LinkCodeRedeem_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ProClient_LinkCodeRedeem_Call) RunAndReturn(run func(context.Context, string, string) (*pro.LinkCodeRedeemResponse, error)) *ProClient_LinkCodeRedeem_Call { + _c.Call.Return(run) + return _c +} + // LinkCodeRequest provides a mock function with given fields: ctx, deviceName func (_m *ProClient) LinkCodeRequest(ctx context.Context, deviceName string) (*pro.LinkCodeResponse, error) { ret := _m.Called(ctx, deviceName) @@ -262,6 +509,35 @@ func (_m *ProClient) LinkCodeRequest(ctx context.Context, deviceName string) (*p return r0, r1 } +// ProClient_LinkCodeRequest_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LinkCodeRequest' +type ProClient_LinkCodeRequest_Call struct { + *mock.Call +} + +// LinkCodeRequest is a helper method to define mock.On call +// - ctx context.Context +// - deviceName string +func (_e *ProClient_Expecter) LinkCodeRequest(ctx interface{}, deviceName interface{}) *ProClient_LinkCodeRequest_Call { + return &ProClient_LinkCodeRequest_Call{Call: _e.mock.On("LinkCodeRequest", ctx, deviceName)} +} + +func (_c *ProClient_LinkCodeRequest_Call) Run(run func(ctx context.Context, deviceName string)) *ProClient_LinkCodeRequest_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *ProClient_LinkCodeRequest_Call) Return(_a0 *pro.LinkCodeResponse, _a1 error) *ProClient_LinkCodeRequest_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ProClient_LinkCodeRequest_Call) RunAndReturn(run func(context.Context, string) (*pro.LinkCodeResponse, error)) *ProClient_LinkCodeRequest_Call { + _c.Call.Return(run) + return _c +} + // PaymentMethods provides a mock function with given fields: ctx func (_m *ProClient) PaymentMethods(ctx context.Context) (*pro.PaymentMethodsResponse, error) { ret := _m.Called(ctx) @@ -292,6 +568,34 @@ func (_m *ProClient) PaymentMethods(ctx context.Context) (*pro.PaymentMethodsRes return r0, r1 } +// ProClient_PaymentMethods_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PaymentMethods' +type ProClient_PaymentMethods_Call struct { + *mock.Call +} + +// PaymentMethods is a helper method to define mock.On call +// - ctx context.Context +func (_e *ProClient_Expecter) PaymentMethods(ctx interface{}) *ProClient_PaymentMethods_Call { + return &ProClient_PaymentMethods_Call{Call: _e.mock.On("PaymentMethods", ctx)} +} + +func (_c *ProClient_PaymentMethods_Call) Run(run func(ctx context.Context)) *ProClient_PaymentMethods_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *ProClient_PaymentMethods_Call) Return(_a0 *pro.PaymentMethodsResponse, _a1 error) *ProClient_PaymentMethods_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ProClient_PaymentMethods_Call) RunAndReturn(run func(context.Context) (*pro.PaymentMethodsResponse, error)) *ProClient_PaymentMethods_Call { + _c.Call.Return(run) + return _c +} + // PaymentMethodsV4 provides a mock function with given fields: ctx func (_m *ProClient) PaymentMethodsV4(ctx context.Context) (*pro.PaymentMethodsResponse, error) { ret := _m.Called(ctx) @@ -322,6 +626,34 @@ func (_m *ProClient) PaymentMethodsV4(ctx context.Context) (*pro.PaymentMethodsR return r0, r1 } +// ProClient_PaymentMethodsV4_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PaymentMethodsV4' +type ProClient_PaymentMethodsV4_Call struct { + *mock.Call +} + +// PaymentMethodsV4 is a helper method to define mock.On call +// - ctx context.Context +func (_e *ProClient_Expecter) PaymentMethodsV4(ctx interface{}) *ProClient_PaymentMethodsV4_Call { + return &ProClient_PaymentMethodsV4_Call{Call: _e.mock.On("PaymentMethodsV4", ctx)} +} + +func (_c *ProClient_PaymentMethodsV4_Call) Run(run func(ctx context.Context)) *ProClient_PaymentMethodsV4_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *ProClient_PaymentMethodsV4_Call) Return(_a0 *pro.PaymentMethodsResponse, _a1 error) *ProClient_PaymentMethodsV4_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ProClient_PaymentMethodsV4_Call) RunAndReturn(run func(context.Context) (*pro.PaymentMethodsResponse, error)) *ProClient_PaymentMethodsV4_Call { + _c.Call.Return(run) + return _c +} + // PaymentRedirect provides a mock function with given fields: ctx, req func (_m *ProClient) PaymentRedirect(ctx context.Context, req *protos.PaymentRedirectRequest) (*pro.PaymentRedirectResponse, error) { ret := _m.Called(ctx, req) @@ -352,6 +684,35 @@ func (_m *ProClient) PaymentRedirect(ctx context.Context, req *protos.PaymentRed return r0, r1 } +// ProClient_PaymentRedirect_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PaymentRedirect' +type ProClient_PaymentRedirect_Call struct { + *mock.Call +} + +// PaymentRedirect is a helper method to define mock.On call +// - ctx context.Context +// - req *protos.PaymentRedirectRequest +func (_e *ProClient_Expecter) PaymentRedirect(ctx interface{}, req interface{}) *ProClient_PaymentRedirect_Call { + return &ProClient_PaymentRedirect_Call{Call: _e.mock.On("PaymentRedirect", ctx, req)} +} + +func (_c *ProClient_PaymentRedirect_Call) Run(run func(ctx context.Context, req *protos.PaymentRedirectRequest)) *ProClient_PaymentRedirect_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*protos.PaymentRedirectRequest)) + }) + return _c +} + +func (_c *ProClient_PaymentRedirect_Call) Return(_a0 *pro.PaymentRedirectResponse, _a1 error) *ProClient_PaymentRedirect_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ProClient_PaymentRedirect_Call) RunAndReturn(run func(context.Context, *protos.PaymentRedirectRequest) (*pro.PaymentRedirectResponse, error)) *ProClient_PaymentRedirect_Call { + _c.Call.Return(run) + return _c +} + // Plans provides a mock function with given fields: ctx func (_m *ProClient) Plans(ctx context.Context) (*pro.PlansResponse, error) { ret := _m.Called(ctx) @@ -382,6 +743,69 @@ func (_m *ProClient) Plans(ctx context.Context) (*pro.PlansResponse, error) { return r0, r1 } +// ProClient_Plans_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Plans' +type ProClient_Plans_Call struct { + *mock.Call +} + +// Plans is a helper method to define mock.On call +// - ctx context.Context +func (_e *ProClient_Expecter) Plans(ctx interface{}) *ProClient_Plans_Call { + return &ProClient_Plans_Call{Call: _e.mock.On("Plans", ctx)} +} + +func (_c *ProClient_Plans_Call) Run(run func(ctx context.Context)) *ProClient_Plans_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *ProClient_Plans_Call) Return(_a0 *pro.PlansResponse, _a1 error) *ProClient_Plans_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ProClient_Plans_Call) RunAndReturn(run func(context.Context) (*pro.PlansResponse, error)) *ProClient_Plans_Call { + _c.Call.Return(run) + return _c +} + +// PollUserData provides a mock function with given fields: ctx, session, maxElapsedTime +func (_m *ProClient) PollUserData(ctx context.Context, session pro.ClientSession, maxElapsedTime time.Duration) { + _m.Called(ctx, session, maxElapsedTime) +} + +// ProClient_PollUserData_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PollUserData' +type ProClient_PollUserData_Call struct { + *mock.Call +} + +// PollUserData is a helper method to define mock.On call +// - ctx context.Context +// - session pro.ClientSession +// - maxElapsedTime time.Duration +func (_e *ProClient_Expecter) PollUserData(ctx interface{}, session interface{}, maxElapsedTime interface{}) *ProClient_PollUserData_Call { + return &ProClient_PollUserData_Call{Call: _e.mock.On("PollUserData", ctx, session, maxElapsedTime)} +} + +func (_c *ProClient_PollUserData_Call) Run(run func(ctx context.Context, session pro.ClientSession, maxElapsedTime time.Duration)) *ProClient_PollUserData_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(pro.ClientSession), args[2].(time.Duration)) + }) + return _c +} + +func (_c *ProClient_PollUserData_Call) Return() *ProClient_PollUserData_Call { + _c.Call.Return() + return _c +} + +func (_c *ProClient_PollUserData_Call) RunAndReturn(run func(context.Context, pro.ClientSession, time.Duration)) *ProClient_PollUserData_Call { + _c.Call.Return(run) + return _c +} + // PostFormReadingJSON provides a mock function with given fields: ctx, path, params, target func (_m *ProClient) PostFormReadingJSON(ctx context.Context, path string, params any, target any) error { ret := _m.Called(ctx, path, params, target) @@ -400,6 +824,37 @@ func (_m *ProClient) PostFormReadingJSON(ctx context.Context, path string, param return r0 } +// ProClient_PostFormReadingJSON_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PostFormReadingJSON' +type ProClient_PostFormReadingJSON_Call struct { + *mock.Call +} + +// PostFormReadingJSON is a helper method to define mock.On call +// - ctx context.Context +// - path string +// - params any +// - target any +func (_e *ProClient_Expecter) PostFormReadingJSON(ctx interface{}, path interface{}, params interface{}, target interface{}) *ProClient_PostFormReadingJSON_Call { + return &ProClient_PostFormReadingJSON_Call{Call: _e.mock.On("PostFormReadingJSON", ctx, path, params, target)} +} + +func (_c *ProClient_PostFormReadingJSON_Call) Run(run func(ctx context.Context, path string, params any, target any)) *ProClient_PostFormReadingJSON_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(any), args[3].(any)) + }) + return _c +} + +func (_c *ProClient_PostFormReadingJSON_Call) Return(_a0 error) *ProClient_PostFormReadingJSON_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *ProClient_PostFormReadingJSON_Call) RunAndReturn(run func(context.Context, string, any, any) error) *ProClient_PostFormReadingJSON_Call { + _c.Call.Return(run) + return _c +} + // PostJSONReadingJSON provides a mock function with given fields: ctx, path, params, body, target func (_m *ProClient) PostJSONReadingJSON(ctx context.Context, path string, params any, body any, target any) error { ret := _m.Called(ctx, path, params, body, target) @@ -418,6 +873,38 @@ func (_m *ProClient) PostJSONReadingJSON(ctx context.Context, path string, param return r0 } +// ProClient_PostJSONReadingJSON_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PostJSONReadingJSON' +type ProClient_PostJSONReadingJSON_Call struct { + *mock.Call +} + +// PostJSONReadingJSON is a helper method to define mock.On call +// - ctx context.Context +// - path string +// - params any +// - body any +// - target any +func (_e *ProClient_Expecter) PostJSONReadingJSON(ctx interface{}, path interface{}, params interface{}, body interface{}, target interface{}) *ProClient_PostJSONReadingJSON_Call { + return &ProClient_PostJSONReadingJSON_Call{Call: _e.mock.On("PostJSONReadingJSON", ctx, path, params, body, target)} +} + +func (_c *ProClient_PostJSONReadingJSON_Call) Run(run func(ctx context.Context, path string, params any, body any, target any)) *ProClient_PostJSONReadingJSON_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(any), args[3].(any), args[4].(any)) + }) + return _c +} + +func (_c *ProClient_PostJSONReadingJSON_Call) Return(_a0 error) *ProClient_PostJSONReadingJSON_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *ProClient_PostJSONReadingJSON_Call) RunAndReturn(run func(context.Context, string, any, any, any) error) *ProClient_PostJSONReadingJSON_Call { + _c.Call.Return(run) + return _c +} + // PostPROTOC provides a mock function with given fields: ctx, path, params, body, target func (_m *ProClient) PostPROTOC(ctx context.Context, path string, params protoreflect.ProtoMessage, body protoreflect.ProtoMessage, target protoreflect.ProtoMessage) error { ret := _m.Called(ctx, path, params, body, target) @@ -436,6 +923,38 @@ func (_m *ProClient) PostPROTOC(ctx context.Context, path string, params protore return r0 } +// ProClient_PostPROTOC_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PostPROTOC' +type ProClient_PostPROTOC_Call struct { + *mock.Call +} + +// PostPROTOC is a helper method to define mock.On call +// - ctx context.Context +// - path string +// - params protoreflect.ProtoMessage +// - body protoreflect.ProtoMessage +// - target protoreflect.ProtoMessage +func (_e *ProClient_Expecter) PostPROTOC(ctx interface{}, path interface{}, params interface{}, body interface{}, target interface{}) *ProClient_PostPROTOC_Call { + return &ProClient_PostPROTOC_Call{Call: _e.mock.On("PostPROTOC", ctx, path, params, body, target)} +} + +func (_c *ProClient_PostPROTOC_Call) Run(run func(ctx context.Context, path string, params protoreflect.ProtoMessage, body protoreflect.ProtoMessage, target protoreflect.ProtoMessage)) *ProClient_PostPROTOC_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(protoreflect.ProtoMessage), args[3].(protoreflect.ProtoMessage), args[4].(protoreflect.ProtoMessage)) + }) + return _c +} + +func (_c *ProClient_PostPROTOC_Call) Return(_a0 error) *ProClient_PostPROTOC_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *ProClient_PostPROTOC_Call) RunAndReturn(run func(context.Context, string, protoreflect.ProtoMessage, protoreflect.ProtoMessage, protoreflect.ProtoMessage) error) *ProClient_PostPROTOC_Call { + _c.Call.Return(run) + return _c +} + // PurchaseRequest provides a mock function with given fields: ctx, data func (_m *ProClient) PurchaseRequest(ctx context.Context, data map[string]interface{}) (*pro.PurchaseResponse, error) { ret := _m.Called(ctx, data) @@ -466,6 +985,35 @@ func (_m *ProClient) PurchaseRequest(ctx context.Context, data map[string]interf return r0, r1 } +// ProClient_PurchaseRequest_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PurchaseRequest' +type ProClient_PurchaseRequest_Call struct { + *mock.Call +} + +// PurchaseRequest is a helper method to define mock.On call +// - ctx context.Context +// - data map[string]interface{} +func (_e *ProClient_Expecter) PurchaseRequest(ctx interface{}, data interface{}) *ProClient_PurchaseRequest_Call { + return &ProClient_PurchaseRequest_Call{Call: _e.mock.On("PurchaseRequest", ctx, data)} +} + +func (_c *ProClient_PurchaseRequest_Call) Run(run func(ctx context.Context, data map[string]interface{})) *ProClient_PurchaseRequest_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(map[string]interface{})) + }) + return _c +} + +func (_c *ProClient_PurchaseRequest_Call) Return(_a0 *pro.PurchaseResponse, _a1 error) *ProClient_PurchaseRequest_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ProClient_PurchaseRequest_Call) RunAndReturn(run func(context.Context, map[string]interface{}) (*pro.PurchaseResponse, error)) *ProClient_PurchaseRequest_Call { + _c.Call.Return(run) + return _c +} + // RedeemResellerCode provides a mock function with given fields: ctx, req func (_m *ProClient) RedeemResellerCode(ctx context.Context, req *protos.RedeemResellerCodeRequest) (*protos.BaseResponse, error) { ret := _m.Called(ctx, req) @@ -496,6 +1044,35 @@ func (_m *ProClient) RedeemResellerCode(ctx context.Context, req *protos.RedeemR return r0, r1 } +// ProClient_RedeemResellerCode_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RedeemResellerCode' +type ProClient_RedeemResellerCode_Call struct { + *mock.Call +} + +// RedeemResellerCode is a helper method to define mock.On call +// - ctx context.Context +// - req *protos.RedeemResellerCodeRequest +func (_e *ProClient_Expecter) RedeemResellerCode(ctx interface{}, req interface{}) *ProClient_RedeemResellerCode_Call { + return &ProClient_RedeemResellerCode_Call{Call: _e.mock.On("RedeemResellerCode", ctx, req)} +} + +func (_c *ProClient_RedeemResellerCode_Call) Run(run func(ctx context.Context, req *protos.RedeemResellerCodeRequest)) *ProClient_RedeemResellerCode_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*protos.RedeemResellerCodeRequest)) + }) + return _c +} + +func (_c *ProClient_RedeemResellerCode_Call) Return(_a0 *protos.BaseResponse, _a1 error) *ProClient_RedeemResellerCode_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ProClient_RedeemResellerCode_Call) RunAndReturn(run func(context.Context, *protos.RedeemResellerCodeRequest) (*protos.BaseResponse, error)) *ProClient_RedeemResellerCode_Call { + _c.Call.Return(run) + return _c +} + // ReferralAttach provides a mock function with given fields: ctx, refCode func (_m *ProClient) ReferralAttach(ctx context.Context, refCode string) (bool, error) { ret := _m.Called(ctx, refCode) @@ -524,6 +1101,35 @@ func (_m *ProClient) ReferralAttach(ctx context.Context, refCode string) (bool, return r0, r1 } +// ProClient_ReferralAttach_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ReferralAttach' +type ProClient_ReferralAttach_Call struct { + *mock.Call +} + +// ReferralAttach is a helper method to define mock.On call +// - ctx context.Context +// - refCode string +func (_e *ProClient_Expecter) ReferralAttach(ctx interface{}, refCode interface{}) *ProClient_ReferralAttach_Call { + return &ProClient_ReferralAttach_Call{Call: _e.mock.On("ReferralAttach", ctx, refCode)} +} + +func (_c *ProClient_ReferralAttach_Call) Run(run func(ctx context.Context, refCode string)) *ProClient_ReferralAttach_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *ProClient_ReferralAttach_Call) Return(_a0 bool, _a1 error) *ProClient_ReferralAttach_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ProClient_ReferralAttach_Call) RunAndReturn(run func(context.Context, string) (bool, error)) *ProClient_ReferralAttach_Call { + _c.Call.Return(run) + return _c +} + // RestorePurchase provides a mock function with given fields: ctx, req func (_m *ProClient) RestorePurchase(ctx context.Context, req map[string]interface{}) (*pro.OkResponse, error) { ret := _m.Called(ctx, req) @@ -554,6 +1160,129 @@ func (_m *ProClient) RestorePurchase(ctx context.Context, req map[string]interfa return r0, r1 } +// ProClient_RestorePurchase_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RestorePurchase' +type ProClient_RestorePurchase_Call struct { + *mock.Call +} + +// RestorePurchase is a helper method to define mock.On call +// - ctx context.Context +// - req map[string]interface{} +func (_e *ProClient_Expecter) RestorePurchase(ctx interface{}, req interface{}) *ProClient_RestorePurchase_Call { + return &ProClient_RestorePurchase_Call{Call: _e.mock.On("RestorePurchase", ctx, req)} +} + +func (_c *ProClient_RestorePurchase_Call) Run(run func(ctx context.Context, req map[string]interface{})) *ProClient_RestorePurchase_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(map[string]interface{})) + }) + return _c +} + +func (_c *ProClient_RestorePurchase_Call) Return(_a0 *pro.OkResponse, _a1 error) *ProClient_RestorePurchase_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ProClient_RestorePurchase_Call) RunAndReturn(run func(context.Context, map[string]interface{}) (*pro.OkResponse, error)) *ProClient_RestorePurchase_Call { + _c.Call.Return(run) + return _c +} + +// RetryCreateUser provides a mock function with given fields: ctx, ss, maxElapsedTime +func (_m *ProClient) RetryCreateUser(ctx context.Context, ss pro.ClientSession, maxElapsedTime time.Duration) { + _m.Called(ctx, ss, maxElapsedTime) +} + +// ProClient_RetryCreateUser_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RetryCreateUser' +type ProClient_RetryCreateUser_Call struct { + *mock.Call +} + +// RetryCreateUser is a helper method to define mock.On call +// - ctx context.Context +// - ss pro.ClientSession +// - maxElapsedTime time.Duration +func (_e *ProClient_Expecter) RetryCreateUser(ctx interface{}, ss interface{}, maxElapsedTime interface{}) *ProClient_RetryCreateUser_Call { + return &ProClient_RetryCreateUser_Call{Call: _e.mock.On("RetryCreateUser", ctx, ss, maxElapsedTime)} +} + +func (_c *ProClient_RetryCreateUser_Call) Run(run func(ctx context.Context, ss pro.ClientSession, maxElapsedTime time.Duration)) *ProClient_RetryCreateUser_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(pro.ClientSession), args[2].(time.Duration)) + }) + return _c +} + +func (_c *ProClient_RetryCreateUser_Call) Return() *ProClient_RetryCreateUser_Call { + _c.Call.Return() + return _c +} + +func (_c *ProClient_RetryCreateUser_Call) RunAndReturn(run func(context.Context, pro.ClientSession, time.Duration)) *ProClient_RetryCreateUser_Call { + _c.Call.Return(run) + return _c +} + +// UpdateUserData provides a mock function with given fields: ctx, ss +func (_m *ProClient) UpdateUserData(ctx context.Context, ss pro.ClientSession) (*protos.User, error) { + ret := _m.Called(ctx, ss) + + if len(ret) == 0 { + panic("no return value specified for UpdateUserData") + } + + var r0 *protos.User + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, pro.ClientSession) (*protos.User, error)); ok { + return rf(ctx, ss) + } + if rf, ok := ret.Get(0).(func(context.Context, pro.ClientSession) *protos.User); ok { + r0 = rf(ctx, ss) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*protos.User) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, pro.ClientSession) error); ok { + r1 = rf(ctx, ss) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ProClient_UpdateUserData_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateUserData' +type ProClient_UpdateUserData_Call struct { + *mock.Call +} + +// UpdateUserData is a helper method to define mock.On call +// - ctx context.Context +// - ss pro.ClientSession +func (_e *ProClient_Expecter) UpdateUserData(ctx interface{}, ss interface{}) *ProClient_UpdateUserData_Call { + return &ProClient_UpdateUserData_Call{Call: _e.mock.On("UpdateUserData", ctx, ss)} +} + +func (_c *ProClient_UpdateUserData_Call) Run(run func(ctx context.Context, ss pro.ClientSession)) *ProClient_UpdateUserData_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(pro.ClientSession)) + }) + return _c +} + +func (_c *ProClient_UpdateUserData_Call) Return(_a0 *protos.User, _a1 error) *ProClient_UpdateUserData_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ProClient_UpdateUserData_Call) RunAndReturn(run func(context.Context, pro.ClientSession) (*protos.User, error)) *ProClient_UpdateUserData_Call { + _c.Call.Return(run) + return _c +} + // UserCreate provides a mock function with given fields: ctx func (_m *ProClient) UserCreate(ctx context.Context) (*pro.UserDataResponse, error) { ret := _m.Called(ctx) @@ -584,6 +1313,34 @@ func (_m *ProClient) UserCreate(ctx context.Context) (*pro.UserDataResponse, err return r0, r1 } +// ProClient_UserCreate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UserCreate' +type ProClient_UserCreate_Call struct { + *mock.Call +} + +// UserCreate is a helper method to define mock.On call +// - ctx context.Context +func (_e *ProClient_Expecter) UserCreate(ctx interface{}) *ProClient_UserCreate_Call { + return &ProClient_UserCreate_Call{Call: _e.mock.On("UserCreate", ctx)} +} + +func (_c *ProClient_UserCreate_Call) Run(run func(ctx context.Context)) *ProClient_UserCreate_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *ProClient_UserCreate_Call) Return(_a0 *pro.UserDataResponse, _a1 error) *ProClient_UserCreate_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ProClient_UserCreate_Call) RunAndReturn(run func(context.Context) (*pro.UserDataResponse, error)) *ProClient_UserCreate_Call { + _c.Call.Return(run) + return _c +} + // UserData provides a mock function with given fields: ctx func (_m *ProClient) UserData(ctx context.Context) (*pro.UserDataResponse, error) { ret := _m.Called(ctx) @@ -614,6 +1371,34 @@ func (_m *ProClient) UserData(ctx context.Context) (*pro.UserDataResponse, error return r0, r1 } +// ProClient_UserData_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UserData' +type ProClient_UserData_Call struct { + *mock.Call +} + +// UserData is a helper method to define mock.On call +// - ctx context.Context +func (_e *ProClient_Expecter) UserData(ctx interface{}) *ProClient_UserData_Call { + return &ProClient_UserData_Call{Call: _e.mock.On("UserData", ctx)} +} + +func (_c *ProClient_UserData_Call) Run(run func(ctx context.Context)) *ProClient_UserData_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *ProClient_UserData_Call) Return(_a0 *pro.UserDataResponse, _a1 error) *ProClient_UserData_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ProClient_UserData_Call) RunAndReturn(run func(context.Context) (*pro.UserDataResponse, error)) *ProClient_UserData_Call { + _c.Call.Return(run) + return _c +} + // UserLinkCodeRequest provides a mock function with given fields: ctx, deviceId, email func (_m *ProClient) UserLinkCodeRequest(ctx context.Context, deviceId string, email string) (bool, error) { ret := _m.Called(ctx, deviceId, email) @@ -642,6 +1427,36 @@ func (_m *ProClient) UserLinkCodeRequest(ctx context.Context, deviceId string, e return r0, r1 } +// ProClient_UserLinkCodeRequest_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UserLinkCodeRequest' +type ProClient_UserLinkCodeRequest_Call struct { + *mock.Call +} + +// UserLinkCodeRequest is a helper method to define mock.On call +// - ctx context.Context +// - deviceId string +// - email string +func (_e *ProClient_Expecter) UserLinkCodeRequest(ctx interface{}, deviceId interface{}, email interface{}) *ProClient_UserLinkCodeRequest_Call { + return &ProClient_UserLinkCodeRequest_Call{Call: _e.mock.On("UserLinkCodeRequest", ctx, deviceId, email)} +} + +func (_c *ProClient_UserLinkCodeRequest_Call) Run(run func(ctx context.Context, deviceId string, email string)) *ProClient_UserLinkCodeRequest_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string)) + }) + return _c +} + +func (_c *ProClient_UserLinkCodeRequest_Call) Return(_a0 bool, _a1 error) *ProClient_UserLinkCodeRequest_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ProClient_UserLinkCodeRequest_Call) RunAndReturn(run func(context.Context, string, string) (bool, error)) *ProClient_UserLinkCodeRequest_Call { + _c.Call.Return(run) + return _c +} + // UserLinkValidate provides a mock function with given fields: ctx, code func (_m *ProClient) UserLinkValidate(ctx context.Context, code string) (*pro.UserRecovery, error) { ret := _m.Called(ctx, code) @@ -672,6 +1487,35 @@ func (_m *ProClient) UserLinkValidate(ctx context.Context, code string) (*pro.Us return r0, r1 } +// ProClient_UserLinkValidate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UserLinkValidate' +type ProClient_UserLinkValidate_Call struct { + *mock.Call +} + +// UserLinkValidate is a helper method to define mock.On call +// - ctx context.Context +// - code string +func (_e *ProClient_Expecter) UserLinkValidate(ctx interface{}, code interface{}) *ProClient_UserLinkValidate_Call { + return &ProClient_UserLinkValidate_Call{Call: _e.mock.On("UserLinkValidate", ctx, code)} +} + +func (_c *ProClient_UserLinkValidate_Call) Run(run func(ctx context.Context, code string)) *ProClient_UserLinkValidate_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *ProClient_UserLinkValidate_Call) Return(_a0 *pro.UserRecovery, _a1 error) *ProClient_UserLinkValidate_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ProClient_UserLinkValidate_Call) RunAndReturn(run func(context.Context, string) (*pro.UserRecovery, error)) *ProClient_UserLinkValidate_Call { + _c.Call.Return(run) + return _c +} + // NewProClient creates a new instance of ProClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewProClient(t interface { diff --git a/internalsdk/pro/plans.go b/internalsdk/pro/plans.go new file mode 100644 index 000000000..4ebadca57 --- /dev/null +++ b/internalsdk/pro/plans.go @@ -0,0 +1,81 @@ +package pro + +import ( + "context" + + "github.com/getlantern/errors" + "github.com/getlantern/lantern-client/internalsdk/protos" +) + +// Plans returns the plans available to a user +func (c *proClient) Plans(ctx context.Context) ([]protos.Plan, error) { + if v, ok := c.plansCache.Load("plans"); ok { + resp := v.([]protos.Plan) + log.Debugf("Returning plans from cache %s", v) + return resp, nil + } + resp, err := c.FetchPaymentMethodsAndCache(ctx) + if err != nil { + return nil, err + } + return resp.Plans, nil +} + +// DesktopPaymentMethods returns plans and payment methods available for desktop users +func (c *proClient) DesktopPaymentMethods(ctx context.Context) ([]protos.PaymentMethod, error) { + return c.paymentMethodsByPlatform(ctx, "desktop") +} + +// PaymentMethodsByPlatform returns the plans and payments from cache for the given platform +// if available; if not then call FetchPaymentMethods +func (c *proClient) paymentMethodsByPlatform(ctx context.Context, platform string) ([]protos.PaymentMethod, error) { + if platform != "desktop" && platform != "android" { + return nil, errors.New("invalid platform") + } + if v, ok := c.plansCache.Load("paymentMethods"); ok { + resp := v.([]protos.PaymentMethod) + log.Debugf("Returning payment methods from cache %s", v) + return resp, nil + } + resp, err := c.FetchPaymentMethodsAndCache(ctx) + if err != nil { + return nil, err + } + desktopProviders, ok := resp.Providers["desktop"] + if !ok { + return nil, errors.New("No desktop payment providers found") + } + return desktopProviders, nil +} + +// FetchPaymentMethodsAndCache returns the plans and payment plans available to a user +// Updates cache with the fetched data +func (c *proClient) FetchPaymentMethodsAndCache(ctx context.Context) (*PaymentMethodsResponse, error) { + resp, err := c.PaymentMethodsV4(context.Background()) + if err != nil { + return nil, errors.New("Could not get payment methods: %v", err) + } + desktopPaymentMethods, ok := resp.Providers["desktop"] + if !ok { + return nil, errors.New("No desktop payment providers found") + } + for i := range desktopPaymentMethods { + paymentMethod := &desktopPaymentMethods[i] + for j, provider := range paymentMethod.Providers { + if resp.Logo[provider.Name] != nil { + logos := resp.Logo[provider.Name].([]interface{}) + for _, logo := range logos { + paymentMethod.Providers[j].LogoUrls = append(paymentMethod.Providers[j].LogoUrls, logo.(string)) + } + } + } + } + //clear previous store cache + c.plansCache.Delete("plans") + c.plansCache.Delete("paymentMethods") + log.Debugf("DEBUG: Payment methods plans: %+v", resp.Plans) + log.Debugf("DEBUG: Payment methods providers: %+v", desktopPaymentMethods) + c.plansCache.Store("plans", resp.Plans) + c.plansCache.Store("paymentMethods", desktopPaymentMethods) + return resp, nil +} diff --git a/internalsdk/pro/pro.go b/internalsdk/pro/pro.go index 907a4d2a0..de98aaee3 100644 --- a/internalsdk/pro/pro.go +++ b/internalsdk/pro/pro.go @@ -6,6 +6,7 @@ import ( "fmt" "net/http" "strings" + "sync" "time" "github.com/getlantern/errors" @@ -28,17 +29,28 @@ var ( type proClient struct { webclient.RESTClient - userConfig func() common.UserConfig + backoffRunner *backoffRunner + plansCache sync.Map + userConfig func() common.UserConfig +} + +type Client interface { + UpdateUserData(ctx context.Context, session ClientSession) (*protos.User, error) } type ProClient interface { webclient.RESTClient + Client EmailExists(ctx context.Context, email string) (*protos.BaseResponse, error) + DesktopPaymentMethods(ctx context.Context) ([]protos.PaymentMethod, error) PaymentMethods(ctx context.Context) (*PaymentMethodsResponse, error) PaymentMethodsV4(ctx context.Context) (*PaymentMethodsResponse, error) PaymentRedirect(ctx context.Context, req *protos.PaymentRedirectRequest) (*PaymentRedirectResponse, error) - Plans(ctx context.Context) (*PlansResponse, error) + FetchPaymentMethodsAndCache(ctx context.Context) (*PaymentMethodsResponse, error) + Plans(ctx context.Context) ([]protos.Plan, error) + PollUserData(ctx context.Context, session ClientSession, maxElapsedTime time.Duration, client Client) RedeemResellerCode(ctx context.Context, req *protos.RedeemResellerCodeRequest) (*protos.BaseResponse, error) + RetryCreateUser(ctx context.Context, ss ClientSession, maxElapsedTime time.Duration) UserCreate(ctx context.Context) (*UserDataResponse, error) UserData(ctx context.Context) (*UserDataResponse, error) PurchaseRequest(ctx context.Context, data map[string]interface{}) (*PurchaseResponse, error) @@ -58,7 +70,8 @@ type ProClient interface { // NewClient creates a new instance of ProClient func NewClient(baseURL string, userConfig func() common.UserConfig) ProClient { return &proClient{ - userConfig: userConfig, + userConfig: userConfig, + backoffRunner: &backoffRunner{}, RESTClient: webclient.NewRESTClient(&webclient.Opts{ // The default http.RoundTripper used by the ProClient is ParallelForIdempotent which // attempts to send requests through both chained and direct fronted routes in parallel @@ -160,7 +173,8 @@ func (c *proClient) PaymentMethodsV4(ctx context.Context) (*PaymentMethodsRespon } // process plans for currency - for i, plan := range resp.Plans { + for i := range resp.Plans { + plan := &resp.Plans[i] parts := strings.Split(plan.Id, "-") if len(parts) != 3 { continue @@ -174,33 +188,9 @@ func (c *proClient) PaymentMethodsV4(ctx context.Context) (*PaymentMethodsRespon amount := decimal.NewFromInt(monthlyPrice).Div(decimal.NewFromInt(100)) yearAmount := decimal.NewFromInt(yearlyPrice).Div(decimal.NewFromInt(100)) - resp.Plans[i].OneMonthCost = ac.FormatMoneyDecimal(amount) - resp.Plans[i].TotalCost = ac.FormatMoneyDecimal(yearAmount) - resp.Plans[i].TotalCostBilledOneTime = fmt.Sprintf("%v billed one time", ac.FormatMoneyDecimal(yearAmount)) - } - return &resp, nil -} - -// Plans is used to hit the legacy /plans endpoint. Deprecated. -func (c *proClient) Plans(ctx context.Context) (*PlansResponse, error) { - var resp PlansResponse - err := c.GetJSON(ctx, "/plans", c.defaultParams(), &resp) - if err != nil { - return nil, err - } - for i, plan := range resp.Plans { - parts := strings.Split(plan.Id, "-") - if len(parts) != 3 { - continue - } - cur := parts[1] - if currency, ok := accounting.LocaleInfo[strings.ToUpper(cur)]; ok { - if oneMonthCost, ok2 := plan.ExpectedMonthlyPrice[strings.ToLower(cur)]; ok2 { - ac := accounting.Accounting{Symbol: currency.ComSymbol, Precision: 2} - amount := decimal.NewFromInt(oneMonthCost).Div(decimal.NewFromInt(100)) - resp.Plans[i].OneMonthCost = ac.FormatMoneyDecimal(amount) - } - } + plan.OneMonthCost = ac.FormatMoneyDecimal(amount) + plan.TotalCost = ac.FormatMoneyDecimal(yearAmount) + plan.TotalCostBilledOneTime = fmt.Sprintf("%v billed one time", ac.FormatMoneyDecimal(yearAmount)) } return &resp, nil } diff --git a/internalsdk/pro/user.go b/internalsdk/pro/user.go new file mode 100644 index 000000000..ad7be8ba3 --- /dev/null +++ b/internalsdk/pro/user.go @@ -0,0 +1,176 @@ +package pro + +import ( + "context" + "sync" + "time" + + "github.com/cenkalti/backoff/v4" + "github.com/getlantern/errors" + "github.com/getlantern/lantern-client/internalsdk/protos" +) + +type ClientSession interface { + SetExpiration(int64) error + SetProUser(bool) error + SetReferralCode(string) error + SetUserIDAndToken(int64, string) error + FetchUserData() error + GetDeviceID() (string, error) + GetUserFirstVisit() (bool, error) +} + +type backoffRunner struct { + mu sync.Mutex + isRunning bool +} + +// createUser submits a request to create a new user with the Pro user and +// configures a new client session +func (c *proClient) createUser(ctx context.Context, session ClientSession) error { + log.Debug("New user, calling user create") + resp, err := c.UserCreate(ctx) + if err != nil { + return errors.New("Could not create new Pro user: %v", err) + } + user := resp.User + if resp.BaseResponse != nil && resp.BaseResponse.Error != "" { + return errors.New("Could not create new Pro user: %v", err) + } + log.Debugf("Successfully created new user with id %d", user.UserId) + session.SetReferralCode(user.Referral) + session.SetUserIDAndToken(user.UserId, user.Token) + session.FetchUserData() + return nil +} + +// RetryCreateUser is used to retry creating a user with an exponential backoff strategy +func (c *proClient) RetryCreateUser(ctx context.Context, ss ClientSession, maxElapsedTime time.Duration) { + log.Debug("Starting retry handler for user creation") + expBackoff := backoff.NewExponentialBackOff() + expBackoff.Multiplier = 2.0 + expBackoff.InitialInterval = 3 * time.Second + expBackoff.MaxInterval = 1 * time.Minute + expBackoff.MaxElapsedTime = maxElapsedTime + expBackoff.RandomizationFactor = 0.5 + err := backoff.Retry(func() error { + return c.createUser(ctx, ss) + }, backoff.WithContext(expBackoff, ctx)) + if err != nil { + log.Fatal("Unable to create Lantern user after max retries") + } +} + +func (c *proClient) UpdateUserData(ctx context.Context, ss ClientSession) (*protos.User, error) { + resp, err := c.UserData(ctx) + if err != nil { + return nil, errors.New("error fetching user data: %v", err) + } else if resp.User == nil { + return nil, errors.New("error fetching user data") + } + user := resp.User + currentDevice, err := ss.GetDeviceID() + if err != nil { + return nil, log.Errorf("error fetching device id: %v", err) + } + + // Check if device id is connect to same device if not create new user + // this is for the case when user removed device from other device + deviceFound := false + if user.Devices != nil { + for _, device := range user.Devices { + if device.Id == currentDevice { + deviceFound = true + break + } + } + } + /// Check if user has installed app first time + firstTime, err := ss.GetUserFirstVisit() + if err != nil { + return nil, log.Errorf("error fetching user first visit: %v", err) + } + + log.Debugf("First time visit %v", firstTime) + if user.UserLevel == "pro" && firstTime { + log.Debugf("User is pro and first time") + ss.SetProUser(true) + } else if user.UserLevel == "pro" && !firstTime && deviceFound { + log.Debugf("User is pro and not first time") + ss.SetProUser(true) + } else { + log.Debugf("User is not pro") + ss.SetProUser(false) + } + ss.SetUserIDAndToken(user.UserId, user.Token) + ss.SetExpiration(user.Expiration) + ss.SetReferralCode(user.Referral) + return user, nil +} + +// PollUserData polls for user data with a retry handler up to max elapsed time +func (c *proClient) PollUserData(ctx context.Context, session ClientSession, + maxElapsedTime time.Duration, client Client) { + log.Debug("Polling user data") + b := c.backoffRunner + b.mu.Lock() + if b.isRunning { + b.mu.Unlock() + return + } + b.isRunning = true + b.mu.Unlock() + + ctx, cancel := context.WithTimeout(ctx, maxElapsedTime) + defer func() { + cancel() + b.mu.Lock() + b.isRunning = false + b.mu.Unlock() + }() + + expBackoff := backoff.NewExponentialBackOff() + expBackoff.Multiplier = 2.0 + expBackoff.InitialInterval = 10 * time.Second + expBackoff.MaxInterval = 2 * time.Minute + expBackoff.MaxElapsedTime = maxElapsedTime + // Add jitter to backoff interval + expBackoff.RandomizationFactor = 0.5 + + timer := time.NewTimer(expBackoff.NextBackOff()) + defer timer.Stop() + + for { + select { + case <-ctx.Done(): + log.Errorf("Poll user data cancelled: %v", ctx.Err()) + return + case <-timer.C: + user, err := client.UpdateUserData(ctx, session) + if err != nil { + if ctx.Err() != nil { + log.Errorf("UpdateUserData terminated due to context: %v", ctx.Err()) + return + } + log.Errorf("UpdateUserData failed: %v", err) + } + + userIsPro := func(u *protos.User) bool { + return u != nil && (u.UserLevel == "pro" || u.UserStatus == "active") + } + + if userIsPro(user) { + log.Debug("User became Pro. Stopping polling.") + return + } + + // Get the next backoff interval + waitTime := expBackoff.NextBackOff() + if waitTime == backoff.Stop { + log.Debug("Exponential backoff reached max elapsed time. Exiting...") + return + } + timer.Reset(waitTime) + } + } +} diff --git a/internalsdk/pro/user_test.go b/internalsdk/pro/user_test.go new file mode 100644 index 000000000..1bde36591 --- /dev/null +++ b/internalsdk/pro/user_test.go @@ -0,0 +1,86 @@ +package pro + +import ( + "context" + "testing" + "time" + + "github.com/getlantern/errors" + "github.com/getlantern/lantern-client/internalsdk/protos" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +type mockProClient struct { + mock.Mock +} + +func (m *mockProClient) UpdateUserData(ctx context.Context, session ClientSession) (*protos.User, error) { + args := m.Called(ctx, session) + user, _ := args.Get(0).(*protos.User) + return user, args.Error(1) +} + +// Test PollUserData +func TestPollUserData(t *testing.T) { + mockClient := new(mockProClient) + var session ClientSession + + mockClient.On("UpdateUserData", mock.Anything, session).Return(nil, nil) + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + c := &proClient{ + backoffRunner: &backoffRunner{}, + } + + // Run PollUserData + go c.PollUserData(ctx, session, 10*time.Second, mockClient) + + time.Sleep(12 * time.Second) + + // Count the number of calls + callCount := 0 + for _, call := range mockClient.Calls { + if call.Method == "UpdateUserData" { + callCount++ + } + } + + // Verify the minimum number of calls + assert.GreaterOrEqual(t, callCount, 2, "Expected UpdateUserData to be called at least twice") +} + +// Test PollUserData (Handles Errors) +func TestPollUserDataWithError(t *testing.T) { + mockClient := new(mockProClient) + var session ClientSession + + // Configure the mock client to simulate errors + mockClient.On("UpdateUserData", mock.Anything, session).Return(nil, errors.New("mock error")) + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + c := &proClient{ + backoffRunner: &backoffRunner{}, + } + + go c.PollUserData(ctx, session, 5*time.Second, mockClient) + + time.Sleep(6 * time.Second) + + // Verify that UpdateUserData was retried multiple times + AssertAtLeastCalled(t, &mockClient.Mock, "UpdateUserData", 2) +} + +func AssertAtLeastCalled(t *testing.T, mock *mock.Mock, methodName string, minCalls int) { + callCount := 0 + for _, call := range mock.Calls { + if call.Method == methodName { + callCount++ + } + } + assert.GreaterOrEqual(t, callCount, minCalls, "Expected at least %d calls to %s, but got %d", minCalls, methodName, callCount) +} diff --git a/internalsdk/session_model.go b/internalsdk/session_model.go index 233388816..f7dc06b46 100644 --- a/internalsdk/session_model.go +++ b/internalsdk/session_model.go @@ -188,6 +188,7 @@ func NewSessionModel(mdb minisql.DB, opts *SessionModelOpts) (*SessionModel, err if opts.Platform == "ios" { go m.setupIosConfigure(opts.ConfigPath, int(userID), token, deviceID) } + log.Debugf("SessionModel initialized") go m.initSessionModel(context.Background(), opts) return m, nil } @@ -246,7 +247,7 @@ func (m *SessionModel) doInvokeMethod(method string, arguments Arguments) (inter } return true, nil case "setProUser": - err := setProUser(m.baseModel, arguments.Scalar().Bool()) + err := m.SetProUser(arguments.Scalar().Bool()) if err != nil { return nil, err } @@ -329,7 +330,7 @@ func (m *SessionModel) doInvokeMethod(method string, arguments Arguments) (inter case "setUserIdAndToken": userId := arguments.Get("userId").Int() token := arguments.Get("token").String() - err := m.SetUserIdAndToken(int64(userId), token) + err := m.SetUserIDAndToken(int64(userId), token) if err != nil { return nil, err } @@ -739,25 +740,27 @@ func (m *SessionModel) initSessionModel(ctx context.Context, opts *SessionModelO pathdb.Mutate(m.db, func(tx pathdb.TX) error { return pathdb.Put(tx, pathIsFirstTime, true, "") }) - err = m.userCreate(ctx) + + // err = m.userCreate(ctx) + // if err != nil { + // log.Error(err) + // } + go m.proClient.RetryCreateUser(ctx, m, 10*time.Minute) + } else { + // Get all user details + err = m.userDetail(ctx) if err != nil { log.Error(err) } - } - // Get all user details - err = m.userDetail(ctx) - if err != nil { - log.Error(err) + go func() { + err = m.paymentMethods() + if err != nil { + log.Debugf("Plans V3 error: %v", err) + // return err + } + }() } - - go func() { - err = m.paymentMethods() - if err != nil { - log.Debugf("Plans V3 error: %v", err) - // return err - } - }() go checkSplitTunneling(m) m.surveyModel, _ = NewSurveyModel(*m) return nil @@ -968,7 +971,7 @@ func setUserLevel(m *baseModel, userLevel string) error { return pathdb.Put(tx, pathUserLevel, userLevel, "") }) } -func setExpiration(m *baseModel, expiration int64) error { +func (m *SessionModel) SetExpiration(expiration int64) error { if expiration == 0 { return nil } @@ -1219,12 +1222,18 @@ func (m *SessionModel) IsProUser() (bool, error) { return pathdb.Get[bool](m.db, pathProUser) } -func setProUser(m *baseModel, isPro bool) error { +func (m *SessionModel) SetProUser(isPro bool) error { return pathdb.Mutate(m.db, func(tx pathdb.TX) error { return pathdb.Put(tx, pathProUser, isPro, "") }) } +func (m *SessionModel) SetReferralCode(referralCode string) error { + return pathdb.Mutate(m.db, func(tx pathdb.TX) error { + return pathdb.Put(tx, pathReferralCode, referralCode, "") + }) +} + func (m *SessionModel) SetReplicaAddr(replicaAddr string) { log.Debugf("Setting replica address %v", replicaAddr) panicIfNecessary(pathdb.Mutate(m.db, func(tx pathdb.TX) error { @@ -1321,6 +1330,10 @@ func (session *SessionModel) updateVpnPref(prefVPN bool) error { }) } +func (m *SessionModel) GetUserFirstVisit() (bool, error) { + return pathdb.Get[bool](m.db, pathIsFirstTime) +} + func checkFirstTimeVisit(m *baseModel) (bool, error) { firsttime, err := pathdb.Get[bool](m.db, pathIsFirstTime) if err != nil { @@ -1339,7 +1352,7 @@ func isShowFirstTimeUserVisit(m *baseModel) error { // Keep name as p1,p2 somehow is conflicting with objective c // p1 is userid and p2 is token -func (m *SessionModel) SetUserIdAndToken(p1 int64, p2 string) error { +func (m *SessionModel) SetUserIDAndToken(p1 int64, p2 string) error { log.Debugf("Setting user id %v token %v", p1, p2) return pathdb.Mutate(m.db, func(tx pathdb.TX) error { if err := pathdb.Put[int64](tx, pathUserID, p1, ""); err != nil { @@ -1354,6 +1367,12 @@ func (m *SessionModel) SetUserIdAndToken(p1 int64, p2 string) error { return pathdb.Put(tx, pathToken, p2, "") }) } + +func (m *SessionModel) FetchUserData() error { + m.proClient.UserData(context.Background()) + return m.paymentMethods() +} + func setResellerCode(m *baseModel, resellerCode string) error { return pathdb.Mutate(m.db, func(tx pathdb.TX) error { return pathdb.Put(tx, pathResellerCode, resellerCode, "") @@ -1393,7 +1412,7 @@ func (session *SessionModel) userCreate(ctx context.Context) error { } //Save user id and token - err = session.SetUserIdAndToken(int64(user.UserId), user.Token) + err = session.SetUserIDAndToken(int64(user.UserId), user.Token) if err != nil { return err } @@ -1419,10 +1438,10 @@ func (session *SessionModel) userDetail(ctx context.Context) error { if logged { userDetail.Email = "" } - return cacheUserDetail(session, userDetail) + return session.cacheUserDetail(userDetail) } -func cacheUserDetail(session *SessionModel, userDetail *protos.User) error { +func (session *SessionModel) cacheUserDetail(userDetail *protos.User) error { if userDetail.Email != "" { setEmail(session.baseModel, userDetail.Email) } @@ -1439,7 +1458,7 @@ func cacheUserDetail(session *SessionModel, userDetail *protos.User) error { return err } - err = setExpiration(session.baseModel, userDetail.Expiration) + err = session.SetExpiration(userDetail.Expiration) if err != nil { return err } @@ -1469,15 +1488,15 @@ func cacheUserDetail(session *SessionModel, userDetail *protos.User) error { log.Debugf("First time visit %v", firstTime) if userDetail.UserLevel == "pro" && firstTime { log.Debugf("User is pro and first time") - setProUser(session.baseModel, true) + session.SetProUser(true) } else if userDetail.UserLevel == "pro" && !firstTime && deviceFound { log.Debugf("User is pro and not first time") - setProUser(session.baseModel, true) + session.SetProUser(true) } else if userDetail.UserLevel == "pro" { log.Debugf("user is pro and device not found") - setProUser(session.baseModel, true) + session.SetProUser(true) } else { - setProUser(session.baseModel, false) + session.SetProUser(false) } //Store all device @@ -1486,7 +1505,7 @@ func cacheUserDetail(session *SessionModel, userDetail *protos.User) error { return err } log.Debugf("User caching successful: %+v", userDetail) - return session.SetUserIdAndToken(int64(userDetail.UserId), userDetail.Token) + return session.SetUserIDAndToken(int64(userDetail.UserId), userDetail.Token) } func reportIssue(session *SessionModel, email string, issue string, description string) error { @@ -1587,7 +1606,7 @@ func redeemResellerCode(m *SessionModel, email string, resellerCode string) erro log.Debugf("Purchase Request response %v", purchase) // Set user to pro - return setProUser(m.baseModel, true) + return m.SetProUser(true) } // Payment Methods @@ -1610,7 +1629,7 @@ func submitApplePayPayment(m *SessionModel, email string, planId string, purchas return errors.New("Purchase Request failed") } // Set user to pro - return setProUser(m.baseModel, true) + return m.SetProUser(true) } func restorePurchase(session *SessionModel, email string, code string, provider string) error { @@ -1632,7 +1651,7 @@ func restorePurchase(session *SessionModel, email string, code string, provider if okResponse.Status != "ok" { return errors.New("error restoring purchase") } - setProUser(session.baseModel, true) + session.SetProUser(true) return nil } @@ -1654,7 +1673,7 @@ func submitGooglePlayPayment(m *SessionModel, email string, planId string, purch log.Debugf("Purchase response %v", purchase) // Set user to pro - return setProUser(m.baseModel, true) + return m.SetProUser(true) } func submitStripePlayPayment(m *SessionModel, email string, planId string, purchaseToken string) error { @@ -1675,7 +1694,7 @@ func submitStripePlayPayment(m *SessionModel, email string, planId string, purch } log.Debugf("Purchase response %v", purchase) // Set user to pro - return setProUser(m.baseModel, true) + return m.SetProUser(true) } func (session *SessionModel) applyRefCode(refCode string) error { @@ -1715,7 +1734,7 @@ func testProviderRequest(session *SessionModel, email string, paymentProvider st if err != nil { return err } - return setProUser(session.baseModel, true) + return session.SetProUser(true) } /// Auth APIS @@ -1819,7 +1838,7 @@ func login(session *SessionModel, email string, password string) error { // once login is successfull save user details // but overide there email with login email userData.Email = email - err = cacheUserDetail(session, userData) + err = session.cacheUserDetail(userData) if err != nil { log.Errorf("Error while caching user details %v", err) return err @@ -1867,7 +1886,7 @@ func deviceLimitFlow(session *SessionModel, login *protos.LoginResponse) error { if err != nil { return err } - return session.SetUserIdAndToken(login.LegacyID, login.LegacyToken) + return session.SetUserIDAndToken(login.LegacyID, login.LegacyToken) } func startRecoveryByEmail(session *SessionModel, email string) error { @@ -2243,7 +2262,7 @@ func linkCodeRedeem(session *SessionModel) error { return err } log.Debugf("linkCodeRedeem response %+v", linkRedeemResponse) - err = session.SetUserIdAndToken(linkRedeemResponse.UserID, linkRedeemResponse.Token) + err = session.SetUserIDAndToken(linkRedeemResponse.UserID, linkRedeemResponse.Token) if err != nil { return log.Errorf("Error while setting user id and token %v", err) } @@ -2314,7 +2333,7 @@ func validateDeviceRecoveryCode(session *SessionModel, code string) error { return err } log.Debugf("ValidateRecovery code response %v", linkResponse) - err = session.SetUserIdAndToken(linkResponse.UserID, linkResponse.Token) + err = session.SetUserIDAndToken(linkResponse.UserID, linkResponse.Token) if err != nil { return err } diff --git a/internalsdk/user.go b/internalsdk/user.go index a1f486220..9ebce511c 100644 --- a/internalsdk/user.go +++ b/internalsdk/user.go @@ -1,25 +1,11 @@ package internalsdk import ( - "context" "encoding/json" - "time" - "github.com/cenkalti/backoff/v4" - "github.com/getlantern/errors" "github.com/getlantern/lantern-client/internalsdk/common" - "github.com/getlantern/lantern-client/internalsdk/pro" ) -// ClientSession includes information needed to create a new client session -type ClientSession interface { - GetDeviceID() (string, error) - GetUserID() (int64, error) - GetToken() (string, error) - Locale() (string, error) - SetUserIdAndToken(int64, string) error -} - type userConfig struct { session PanickingSession } @@ -52,48 +38,3 @@ func (uc *userConfig) GetInternalHeaders() map[string]string { } return h } - -func newUserConfig(session PanickingSession) *userConfig { - return &userConfig{session: session} -} - -func createUser(ctx context.Context, proClient pro.ProClient, session ClientSession) error { - resp, err := proClient.UserCreate(ctx) - if err != nil { - log.Errorf("Error sending request: %v", err) - return err - } - user := resp.User - if user == nil || user.UserId == 0 { - log.Errorf("User not found in response") - return errors.New("User not found in response") - } - - // Save user id and token - session.SetUserIdAndToken(int64(user.UserId), user.Token) - log.Debugf("Created new Lantern user: %+v", user) - return nil -} - -// retryCreateUser is used to retry creating a user with an exponential backoff strategy -func retryCreateUser(ctx context.Context, session ClientSession) { - expBackoff := backoff.NewExponentialBackOff() - expBackoff.Multiplier = 2.0 - expBackoff.InitialInterval = 3 * time.Second - expBackoff.MaxInterval = 1 * time.Minute - expBackoff.MaxElapsedTime = 10 * time.Minute - expBackoff.RandomizationFactor = 0.5 // Add jitter to backoff interval - proClient := createProClient(session, "android") - // Start retrying with exponential backoff - err := backoff.Retry(func() error { - err := createUser(ctx, proClient, session) - if err != nil { - return log.Errorf("Unable to create Lantern user: %v", err) - } - log.Debug("Successfully created user") - return nil - }, backoff.WithContext(expBackoff, ctx)) - if err != nil { - log.Fatal("Unable to create Lantern user after max retries") - } -} diff --git a/internalsdk/user_test.go b/internalsdk/user_test.go deleted file mode 100644 index 720344db1..000000000 --- a/internalsdk/user_test.go +++ /dev/null @@ -1,45 +0,0 @@ -package internalsdk - -import ( - "context" - "testing" - - "github.com/getlantern/errors" - "github.com/getlantern/lantern-client/internalsdk/mocks" - "github.com/getlantern/lantern-client/internalsdk/pro" - "github.com/getlantern/lantern-client/internalsdk/protos" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -func TestCreateUser_Success(t *testing.T) { - - mockProClient := new(mocks.ProClient) - mockSession := new(mocks.Session) - - // Set up expected behavior - mockProClient.On("UserCreate", mock.Anything).Return(&pro.UserDataResponse{ - User: &protos.User{ - UserId: 123, - Token: "test-token", - }, - }, nil) - mockSession.On("SetUserIdAndToken", int64(123), "test-token").Return(nil) - - err := createUser(context.Background(), mockProClient, mockSession) - assert.NoError(t, err, "expected no error") - // Verify that UserCreate and SetUserIdAndToken were called once - mockProClient.AssertCalled(t, "UserCreate", mock.Anything) - mockSession.AssertCalled(t, "SetUserIdAndToken", int64(123), "test-token") -} - -func TestCreateUser_Failure(t *testing.T) { - mockProClient := new(mocks.ProClient) - mockSession := new(mocks.Session) - - mockProClient.On("UserCreate", mock.Anything).Return(nil, errors.New("failed to create user")) - - err := createUser(context.Background(), mockProClient, mockSession) - assert.Error(t, err, "expected an error") - mockProClient.AssertCalled(t, "UserCreate", mock.Anything) -} diff --git a/internalsdk/utils.go b/internalsdk/utils.go index c2dd80cf7..ad17a2670 100644 --- a/internalsdk/utils.go +++ b/internalsdk/utils.go @@ -25,7 +25,7 @@ import ( ) // createProClient creates a new instance of ProClient with the given client session information -func createProClient(session ClientSession, platform string) pro.ProClient { +func createProClient(session Session, platform string) pro.ProClient { return pro.NewClient(fmt.Sprintf("https://%s", common.ProAPIHost), func() common.UserConfig { internalHeaders := map[string]string{ common.PlatformHeader: platform, diff --git a/lib/core/service/websocket_subscriber.dart b/lib/core/service/websocket_subscriber.dart index f935e2233..142f77d8b 100644 --- a/lib/core/service/websocket_subscriber.dart +++ b/lib/core/service/websocket_subscriber.dart @@ -88,13 +88,9 @@ class WebsocketSubscriber { final userStatus = message['userStatus']; final userLevel = message['userLevel']; final deviceLinkingCode = message['deviceLinkingCode']; - if (userLevel != null) { - if (userLevel == 'pro' || userStatus == 'active') { - sessionModel.proUserNotifier.value = true; - } else { - sessionModel.proUserNotifier.value = false; - } - } + final isLevelPro = userLevel != null && userLevel == 'pro'; + final isStatusPro = userStatus != null && userStatus == 'active'; + sessionModel.proUserNotifier.value = (isLevelPro || isStatusPro); if (deviceLinkingCode != null) { sessionModel.linkingCodeNotifier.value = deviceLinkingCode; } diff --git a/lib/main.dart b/lib/main.dart index a9470117a..b9ed0cde6 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,14 +2,12 @@ import 'dart:ui' as ui; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_driver/driver_extension.dart'; -import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:google_mobile_ads/google_mobile_ads.dart'; import 'package:lantern/app.dart'; +import 'package:lantern/core/service/app_purchase.dart'; import 'package:lantern/core/utils/common.dart'; import 'package:lantern/core/utils/common_desktop.dart'; -import 'package:lantern/core/service/app_purchase.dart'; import 'package:lantern/features/replica/ui/utils.dart'; -import 'package:path_provider/path_provider.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:window_manager/window_manager.dart';