From 97130303b648594e41894dc1210c7ddd5da65762 Mon Sep 17 00:00:00 2001 From: atavism Date: Thu, 18 Apr 2024 15:47:09 -0700 Subject: [PATCH] Pro HTTP client updates (#1033) * do not pass initialize flag when running Lantern * do not pass initialize flag when running Lantern * remove tray manager references in vpn switch widget * remove tray manager references in vpn switch widget * use windows webview * update windows webview * update windows webview * windows webview updates * windows webview updates * windows webview updates * updates to use flutter_windows_webview * update CI branch * use wgh136/flutter_windows_webview * use subosito/flutter-action v2.12.0 * use subosito/flutter-action v2.12.0 * use subosito/flutter-action v2.12.0 * Add expectedMonthlyPrice to plan json * update branch name * update planfromJson * use cannot_sign_in instead of cannot_login * fix issue with blank pro account screen * Add devices and expiryDate * update branch name * Add report_description * Add report_description * save email address and pass currency to paymentRedirect * dart format * use switch statement for desktop os opening webview * initial commit - new go-resty based pro http client * pro client updates * pro client updates * pro client updates * use PostFormReadingJSON in LinkCodeRequest * pro client updates * pro client updates * update flashlight, remove unused * update SendToURL * move settings to separate package * reorder imports * update user is pro check * use ubuntu-latest for now * add websocket service * add websocket service * clean-ups, add comments * apply formatting * Added socket to account menu and * set Localization.locale when language is changed on desktop * updates to use didChangeAppLifecycleState * updates to init websocket when app starts * clean-ups, add comments * updates to websocket service * remove websocket changes to account menu * apply formatting * refresh user data when home page is loaded * remove redundant methods * update onTrayMenuItemClick * clean-ups, do not fetch user data when checking email address * merge latest * update flashlight * clean-ups * Add TestClient * export EmptySettings * rename base api response * update errors package * Update how goveralls is installed * Add changes lost prior to merge * Fix issue with plans and payment redirect responses * fix typo * Add GO111MODULE: off when installing goveralls * Update import list * GO111MODULE auto when installing goveralls * Fixed issue on incorrect endpoint on redeemResellerCode and fixed error handling. * make pro client more generic, remove desktop settings * make pro client more generic, remove desktop settings * remove duplicate line * update pro client test * Update PostJSONReadingJSON to allow specifying query parameters * update ios code to use new pro client * update flashlight * update flashlight to v7.6.73 --------- Co-authored-by: Jigar-f --- .github/workflows/go.yml | 4 +- .github/workflows/release.yml | 2 +- Makefile | 12 +- desktop/app/app.go | 58 +- desktop/app/defaults.go | 6 +- desktop/app/issue.go | 22 +- desktop/app/loconfscanner.go | 9 +- desktop/app/pro.go | 154 +++-- desktop/lib.go | 120 ++-- desktop/{app => settings}/settings.go | 41 +- go.mod | 28 +- go.sum | 69 +- internalsdk/apimodels/api.go | 97 --- internalsdk/apimodels/json_models.go | 44 -- internalsdk/common/const.go | 88 +++ internalsdk/common/headers.go | 168 +++++ internalsdk/common/headers_test.go | 136 ++++ internalsdk/common/lantern_config.go | 15 + internalsdk/common/platform.go | 7 + internalsdk/common/platform_ios.go | 3 + internalsdk/common/user_config.go | 62 ++ internalsdk/common/version.go | 37 ++ internalsdk/pro/pro.go | 244 +++++++ internalsdk/pro/pro_test.go | 37 ++ internalsdk/pro/responses.go | 37 ++ .../pro/webclient/defaultwebclient/rest.go | 64 ++ internalsdk/pro/webclient/webclient.go | 77 +++ internalsdk/protos/vpn.pb.go | 607 ++++++++++++++---- internalsdk/session_model.go | 110 ++-- lib/account/account.dart | 17 +- lib/common/ffi_list_subscriber.dart | 2 +- lib/common/session_model.dart | 23 +- lib/ffi.dart | 24 +- lib/plans/checkout.dart | 2 +- lib/vpn/protos_shared/vpn.pb.dart | 450 ++++++++++--- lib/vpn/protos_shared/vpn.pbjson.dart | 140 +++- protos_shared/vpn.proto | 52 +- pubspec.lock | 52 +- 38 files changed, 2445 insertions(+), 675 deletions(-) rename desktop/{app => settings}/settings.go (93%) delete mode 100644 internalsdk/apimodels/api.go delete mode 100644 internalsdk/apimodels/json_models.go create mode 100644 internalsdk/common/const.go create mode 100644 internalsdk/common/headers.go create mode 100644 internalsdk/common/headers_test.go create mode 100644 internalsdk/common/lantern_config.go create mode 100644 internalsdk/common/platform.go create mode 100644 internalsdk/common/platform_ios.go create mode 100644 internalsdk/common/user_config.go create mode 100644 internalsdk/common/version.go create mode 100644 internalsdk/pro/pro.go create mode 100644 internalsdk/pro/pro_test.go create mode 100644 internalsdk/pro/responses.go create mode 100644 internalsdk/pro/webclient/defaultwebclient/rest.go create mode 100644 internalsdk/pro/webclient/webclient.go diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index f8e7dbe5b..2ce1f00d7 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -31,8 +31,8 @@ jobs: go test -failfast -coverprofile=profile.cov ./... - name: Install goveralls env: - GO111MODULE: off - run: go get github.com/mattn/goveralls + GO111MODULE: auto + run: go install github.com/mattn/goveralls@latest - name: Send coverage env: COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0cfd3cd33..51b49a243 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,7 +2,7 @@ name: Publish releases on: push: - branches: [ main ] + branches: [ atavism/pro-client-updates ] tags: - '*' diff --git a/Makefile b/Makefile index 52d1dda7d..7270ab683 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ INTERNALSDK_FRAMEWORK_NAME = Internalsdk.xcframework codegen: protos routes # You can install the dart protoc support by running 'dart pub global activate protoc_plugin' -protos: lib/vpn/protos_shared/vpn.pb.dart +protos: lib/vpn/protos_shared/vpn.pb.dart internalsdk/protos/vpn.pb.go lib/messaging/protos_flutteronly/messaging.pb.dart: protos_flutteronly/messaging.proto @protoc --dart_out=./lib/messaging --plugin=protoc-gen-dart=$$HOME/.pub-cache/bin/protoc-gen-dart protos_flutteronly/messaging.proto @@ -20,11 +20,11 @@ lib/messaging/protos_flutteronly/messaging.pb.dart: protos_flutteronly/messaging lib/vpn/protos_shared/vpn.pb.dart: protos_shared/vpn.proto @protoc --dart_out=./lib/vpn --plugin=protoc-gen-dart=$$HOME/.pub-cache/bin/protoc-gen-dart protos_shared/vpn.proto -#internalsdk/protos/%.pb.go: protos_shared/%.proto -# @echo "Generating Go protobuf for $<" -# @protoc --plugin=protoc-gen-go=build/protoc-gen-go \ -# --go_out=internalsdk \ -# $< +internalsdk/protos/%.pb.go: protos_shared/%.proto + @echo "Generating Go protobuf for $<" + @protoc --plugin=protoc-gen-go=build/protoc-gen-go \ + --go_out=internalsdk \ + $< internalsdk/protos/vpn.pb.go: protos_shared/vpn.proto @protoc --go_out=internalsdk protos_shared/vpn.proto diff --git a/desktop/app/app.go b/desktop/app/app.go index c83e0f57e..fcdffed7b 100644 --- a/desktop/app/app.go +++ b/desktop/app/app.go @@ -30,8 +30,6 @@ import ( "github.com/getlantern/flashlight/v7/logging" "github.com/getlantern/flashlight/v7/ops" "github.com/getlantern/flashlight/v7/otel" - "github.com/getlantern/flashlight/v7/pro" - "github.com/getlantern/flashlight/v7/pro/client" "github.com/getlantern/flashlight/v7/stats" "github.com/getlantern/golog" "github.com/getlantern/i18n" @@ -45,7 +43,9 @@ import ( uicommon "github.com/getlantern/lantern-client/desktop/common" "github.com/getlantern/lantern-client/desktop/features" "github.com/getlantern/lantern-client/desktop/notifier" + "github.com/getlantern/lantern-client/desktop/settings" "github.com/getlantern/lantern-client/desktop/ws" + proclient "github.com/getlantern/lantern-client/internalsdk/pro" ) var ( @@ -71,7 +71,7 @@ type App struct { configDir string exited eventual.Value analyticsSession analytics.Session - settings *Settings + settings *settings.Settings statsTracker *statsTracker muExitFuncs sync.RWMutex @@ -101,7 +101,7 @@ type App struct { proxiesLock sync.RWMutex issueReporter *issueReporter - proClient *client.Client + proClient proclient.ProClient referralCode string selectedTab Tab stats *stats.Stats @@ -117,7 +117,7 @@ type App struct { } // NewApp creates a new desktop app that initializes the app and acts as a moderator between all desktop components. -func NewApp(flags flashlight.Flags, configDir string, proClient *client.Client, settings *Settings) *App { +func NewApp(flags flashlight.Flags, configDir string, proClient proclient.ProClient, settings *settings.Settings) *App { analyticsSession := newAnalyticsSession(settings) app := &App{ configDir: configDir, @@ -135,19 +135,19 @@ func NewApp(flags flashlight.Flags, configDir string, proClient *client.Client, golog.OnFatal(app.exitOnFatal) app.AddExitFunc("stopping analytics", app.analyticsSession.End) - pro.OnProStatusChange(func(isPro bool, _ bool) { + onProStatusChange(func(isPro bool) { app.statsTracker.SetIsPro(isPro) }) log.Debugf("Using configdir: %v", configDir) - app.issueReporter = newIssueReporter(app.settings, app.getCapturedPackets, app.getProxies) + app.issueReporter = newIssueReporter(app) app.translations.Set(os.DirFS("locale/translation")) return app } -func newAnalyticsSession(settings *Settings) analytics.Session { +func newAnalyticsSession(settings *settings.Settings) analytics.Session { if settings.IsAutoReport() { session := analytics.Start(settings.GetDeviceID(), ApplicationVersion) go func() { @@ -194,7 +194,7 @@ func (app *App) Run(isMain bool) { listenAddr := app.Flags.Addr if listenAddr == "" { - listenAddr = app.settings.getString(SNAddr) + listenAddr = app.settings.GetAddr() } if listenAddr == "" { listenAddr = defaultHTTPProxyAddress @@ -202,7 +202,7 @@ func (app *App) Run(isMain bool) { socksAddr := app.Flags.SocksAddr if socksAddr == "" { - socksAddr = app.settings.getString(SNSOCKSAddr) + socksAddr = app.settings.GetSOCKSAddr() } if socksAddr == "" { socksAddr = defaultSOCKSProxyAddress @@ -239,7 +239,7 @@ func (app *App) Run(isMain bool) { RevisionDate, app.configDir, app.Flags.VPN, - func() bool { return app.settings.getBool(SNDisconnected) }, // check whether we're disconnected + func() bool { return app.settings.GetDisconnected() }, // check whether we're disconnected app.settings.GetProxyAll, func() bool { return false }, // on desktop, we do not allow private hosts app.settings.IsAutoReport, @@ -260,11 +260,11 @@ func (app *App) Run(isMain bool) { app.beforeStart(listenAddr) chProStatusChanged := make(chan bool, 1) - pro.OnProStatusChange(func(isPro bool, _ bool) { + onProStatusChange(func(isPro bool) { chProStatusChanged <- isPro }) chUserChanged := make(chan bool, 1) - app.settings.OnChange(SNUserID, func(v interface{}) { + app.settings.OnChange(settings.SNUserID, func(v interface{}) { chUserChanged <- true }) app.startFeaturesService(geolookup.OnRefresh(), chUserChanged, chProStatusChanged, app.chGlobalConfigChanged) @@ -364,9 +364,10 @@ func (app *App) beforeStart(listenAddr string) { app.Exit(nil) os.Exit(0) } - app.AddExitFunc("stopping loconf scanner", LoconfScanner(app.settings, app.configDir, 4*time.Hour, app.IsProUser, func() string { - return app.AddToken("/img/lantern_logo.png") - })) + app.AddExitFunc("stopping loconf scanner", LoconfScanner(app.settings, app.configDir, 4*time.Hour, + func() (bool, bool) { return app.IsProUser(context.Background()) }, func() string { + return app.AddToken("/img/lantern_logo.png") + })) app.AddExitFunc("stopping notifier", notifier.NotificationsLoop(app.analyticsSession)) } @@ -379,14 +380,14 @@ func (app *App) isFeatureEnabled(features map[string]bool, feature string) bool func (app *App) Connect() { app.analyticsSession.Event("systray-menu", "connect") ops.Begin("connect").End() - app.settings.setBool(SNDisconnected, false) + app.settings.SetDisconnected(false) } // Disconnect turns off proxying func (app *App) Disconnect() { app.analyticsSession.Event("systray-menu", "disconnect") ops.Begin("disconnect").End() - app.settings.setBool(SNDisconnected, true) + app.settings.SetDisconnected(true) } // GetLanguage returns the user language @@ -407,7 +408,7 @@ func (app *App) SetLanguage(lang string) { // OnSettingChange sets a callback cb to get called when attr is changed from server. // When calling multiple times for same attr, only the last one takes effect. -func (app *App) OnSettingChange(attr SettingName, cb func(interface{})) { +func (app *App) OnSettingChange(attr settings.SettingName, cb func(interface{})) { app.settings.OnChange(attr, cb) } @@ -417,7 +418,7 @@ func (app *App) OnStatsChange(fn func(stats.Stats)) { } func (app *App) afterStart(cl *flashlightClient.Client) { - app.OnSettingChange(SNSystemProxy, func(val interface{}) { + app.OnSettingChange(settings.SNSystemProxy, func(val interface{}) { enable := val.(bool) if enable { app.SysproxyOn() @@ -431,12 +432,12 @@ func (app *App) afterStart(cl *flashlightClient.Client) { }) app.AddExitFunc("flushing to opentelemetry", otel.Stop) if addr, ok := flashlightClient.Addr(6 * time.Second); ok { - app.settings.setString(SNAddr, addr) + app.settings.SetAddr(addr.(string)) } else { log.Errorf("Couldn't retrieve HTTP proxy addr in time") } if socksAddr, ok := flashlightClient.Socks5Addr(6 * time.Second); ok { - app.settings.setString(SNSOCKSAddr, socksAddr) + app.settings.SetSOCKSAddr(socksAddr.(string)) } else { log.Errorf("Couldn't retrieve SOCKS proxy addr in time") } @@ -601,7 +602,7 @@ func (app *App) exitOnFatal(err error) { // IsPro indicates whether or not the app is pro func (app *App) IsPro() bool { - isPro, _ := app.isProUserFast() + isPro, _ := app.isProUserFast(context.Background()) return isPro } @@ -609,12 +610,13 @@ func (app *App) IsPro() bool { func (app *App) ReferralCode(uc common.UserConfig) (string, error) { referralCode := app.referralCode if referralCode == "" { - resp, err := app.proClient.UserData(uc) + resp, err := app.proClient.UserData(context.Background()) if err != nil { - return "", err + return "", errors.New("error fetching user data: %v", err) } - app.SetReferralCode(resp.Code) - return resp.Code, nil + + app.SetReferralCode(resp.User.Code) + return resp.User.Code, nil } return referralCode, nil } @@ -678,7 +680,7 @@ func (app *App) AddToken(path string) string { return path } -func (app *App) Settings() *Settings { +func (app *App) Settings() *settings.Settings { return app.settings } diff --git a/desktop/app/defaults.go b/desktop/app/defaults.go index 50e926d54..f4d874c1d 100644 --- a/desktop/app/defaults.go +++ b/desktop/app/defaults.go @@ -6,6 +6,8 @@ package app import ( "crypto/rand" "encoding/hex" + + "github.com/getlantern/lantern-client/desktop/settings" ) const ( @@ -23,7 +25,7 @@ func randRead(size int) string { // localHTTPToken fetches the local HTTP token from disk if it's there, and // otherwise creates a new one and stores it. -func localHTTPToken(set *Settings) string { +func localHTTPToken(set *settings.Settings) string { tok := set.GetLocalHTTPToken() if tok == "" { t := randRead(16) @@ -31,4 +33,4 @@ func localHTTPToken(set *Settings) string { return t } return tok -} \ No newline at end of file +} diff --git a/desktop/app/issue.go b/desktop/app/issue.go index 93b40519e..13108b7f2 100644 --- a/desktop/app/issue.go +++ b/desktop/app/issue.go @@ -1,15 +1,17 @@ package app import ( + "context" "io" "math" "strconv" + "github.com/getlantern/lantern-client/desktop/settings" + "github.com/getlantern/lantern-client/internalsdk/pro" + "github.com/getlantern/flashlight/v7/bandit" "github.com/getlantern/flashlight/v7/common" "github.com/getlantern/flashlight/v7/issue" - "github.com/getlantern/flashlight/v7/pro" - "github.com/getlantern/flashlight/v7/util" "github.com/getlantern/osversion" ) @@ -23,9 +25,10 @@ var ( ) type issueReporter struct { - settings *Settings + settings *settings.Settings getCapturedPackets func(io.Writer) error getProxies func() []bandit.Dialer + proClient pro.ProClient } type issueMessage struct { @@ -44,12 +47,12 @@ type issueMessage struct { // newIssueReporter creates a new issue reporter that can be used to send issue reports // to the Lantern team. -func newIssueReporter(settings *Settings, getCapturedPackets func(io.Writer) error, - getProxies func() []bandit.Dialer) *issueReporter { +func newIssueReporter(app *App) *issueReporter { return &issueReporter{ - settings: settings, - getCapturedPackets: getCapturedPackets, - getProxies: getProxies, + getCapturedPackets: app.getCapturedPackets, + getProxies: app.getProxies, + proClient: app.proClient, + settings: app.settings, } } @@ -80,7 +83,8 @@ func (reporter *issueReporter) sendIssueReport(msg *issueMessage) error { return err } subscriptionLevel := "free" - if isPro, _ := pro.IsProUser(settings); isPro { + ctx := context.Background() + if isPro, _ := IsProUser(ctx, reporter.proClient, settings.GetUserID()); isPro { subscriptionLevel = "pro" } var osVersion string diff --git a/desktop/app/loconfscanner.go b/desktop/app/loconfscanner.go index 2d61e0eb9..53b22ce2c 100644 --- a/desktop/app/loconfscanner.go +++ b/desktop/app/loconfscanner.go @@ -14,6 +14,7 @@ import ( "github.com/getlantern/lantern-client/desktop/loconf" "github.com/getlantern/lantern-client/desktop/notifier" + "github.com/getlantern/lantern-client/desktop/settings" ) // LoconfScanner starts a goroutine to periodically check for new loconf files. @@ -27,7 +28,7 @@ import ( // show the announcement or not). // // Returns a function to stop the loop. -func LoconfScanner(settings *Settings, configDir string, interval time.Duration, proChecker func() (bool, bool), iconURL func() string) (stop func()) { +func LoconfScanner(settings *settings.Settings, configDir string, interval time.Duration, proChecker func() (bool, bool), iconURL func() string) (stop func()) { loc := &loconfer{ log: golog.LoggerFor("loconfer"), configDir: configDir, @@ -86,7 +87,7 @@ type loconfer struct { configDir string r *rand.Rand iconURL func() string - settings *Settings + settings *settings.Settings } func (loc *loconfer) onLoconf(lc *loconf.LoConf, isPro bool) { @@ -139,14 +140,14 @@ func (loc *loconfer) makeAnnouncements(lc *loconf.LoConf, isPro bool) { } return } - past := loc.settings.getStringArray(SNPastAnnouncements) + past := loc.settings.GetPastAnnouncements() if in(current.Campaign, past) { loc.log.Debugf("Skip announcement %s", current.Campaign) return } if loc.showAnnouncement(current) { past = append(past, current.Campaign) - loc.settings.setStringArray(SNPastAnnouncements, past) + loc.settings.SetPastAnnouncements(past) } } diff --git a/desktop/app/pro.go b/desktop/app/pro.go index 021c709ac..a097c2be1 100644 --- a/desktop/app/pro.go +++ b/desktop/app/pro.go @@ -1,34 +1,136 @@ package app import ( + "context" "encoding/json" + "sync" "time" "github.com/getlantern/errors" - "github.com/getlantern/flashlight/v7/pro" - "github.com/getlantern/flashlight/v7/pro/client" - "github.com/getlantern/lantern-client/desktop/deviceid" + "github.com/getlantern/eventual/v2" + "github.com/getlantern/flashlight/v7/common" + "github.com/getlantern/lantern-client/desktop/settings" "github.com/getlantern/lantern-client/desktop/ws" + "github.com/getlantern/lantern-client/internalsdk/pro" + "github.com/getlantern/lantern-client/internalsdk/protos" ) -// isProUser blocks itself to check if current user is Pro, or !ok if error +type userMap struct { + sync.RWMutex + data map[int64]eventual.Value + onUserData []func(current *protos.User, new *protos.User) +} + +var userData = userMap{ + data: make(map[int64]eventual.Value), + onUserData: make([]func(current *protos.User, new *protos.User), 0), +} + +// onUserData allows registering an event handler to learn when the +// user data has been fetched. +func onUserData(cb func(current *protos.User, new *protos.User)) { + userData.Lock() + userData.onUserData = append(userData.onUserData, cb) + userData.Unlock() +} + +// onProStatusChange allows registering an event handler to learn when the +// user's pro status or "yinbi enabled" status has changed. +func onProStatusChange(cb func(isPro bool)) { + onUserData(func(current *protos.User, new *protos.User) { + if current == nil || isActive(current.UserStatus) != isActive(new.UserStatus) { + cb(isActive(new.UserStatus)) + } + }) +} + +func (m *userMap) save(ctx context.Context, userID int64, u *protos.User) { + m.Lock() + v := m.data[userID] + var current *protos.User + if v == nil { + v = eventual.NewValue() + } else { + cur, _ := v.Get(ctx) + current, _ = cur.(*protos.User) + } + v.Set(u) + m.data[userID] = v + onUserData := m.onUserData + m.Unlock() + for _, cb := range onUserData { + cb(current, u) + } +} + +func (m *userMap) get(ctx context.Context, userID int64) (*protos.User, bool) { + m.RLock() + v := m.data[userID] + m.RUnlock() + if v == nil { + return nil, false + } + u, err := v.Get(ctx) + if err != nil { + return nil, false + } + return u.(*protos.User), true +} + +// IsProUser blocks itself to check if current user is Pro, or !ok if error // happens getting user status from pro-server. The result is not cached // because the user can become Pro or free at any time. It waits until // the user ID becomes non-zero. -func (app *App) IsProUser() (isPro bool, ok bool) { - _, err := app.settings.GetInt64Eventually(SNUserID) +func (app *App) IsProUser(ctx context.Context) (isPro bool, ok bool) { + _, err := app.settings.GetInt64Eventually(settings.SNUserID) if err != nil { return false, false } - return pro.IsProUser(app.settings) + return IsProUser(ctx, app.proClient, app.settings.GetUserID()) +} + +func IsProUser(ctx context.Context, proClient pro.ProClient, userId int64) (isPro bool, ok bool) { + isActive := func(user *protos.User) bool { + return user != nil && user.UserStatus == "active" + } + user, found := GetUserDataFast(ctx, userId) + if !found { + ctx := context.Background() + resp, err := proClient.UserData(ctx) + if err != nil { + return false, false + } + user = resp.User + } + return isActive(user), true +} + +// isActive determines whether the given status is an active status +func isActive(status string) bool { + return status == "active" +} + +// GetUserDataFast gets the user data for the given userID if found. +func GetUserDataFast(ctx context.Context, userID int64) (*protos.User, bool) { + return userData.get(ctx, userID) +} + +// IsProUserFast indicates whether or not the user is pro and whether or not the +// user's status is know, never calling the Pro API to determine the status. +func IsProUserFast(ctx context.Context, uc common.UserConfig) (isPro bool, statusKnown bool) { + user, found := GetUserDataFast(ctx, uc.GetUserID()) + if !found { + return false, false + } + return isActive(user.UserStatus), found } // isProUserFast checks a cached value for the pro status and doesn't wait for // an answer. It works because servePro below fetches user data / create new // user when starts up. The pro proxy also updates user data implicitly for // '/userData' calls initiated from desktop UI. -func (app *App) isProUserFast() (isPro bool, statusKnown bool) { - return pro.IsProUserFast(app.settings) +func (app *App) isProUserFast(ctx context.Context) (isPro bool, statusKnown bool) { + return IsProUserFast(ctx, app.settings) } // servePro fetches user data or creates new user when the application starts up @@ -36,40 +138,18 @@ func (app *App) isProUserFast() (isPro bool, statusKnown bool) { // 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 { - user, err := pro.NewUser(app.settings) + resp, err := app.proClient.UserCreate(ctx) if err != nil { return errors.New("Could not create new Pro user: %v", err) } - app.settings.SetUserIDAndToken(user.Auth.ID, user.Auth.Token) + app.settings.SetUserIDAndToken(resp.User.UserId, resp.User.Token) } else { - isPro, _ := pro.IsProUserFast(app.settings) - if isPro && userID != app.settings.GetMigratedDeviceIDForUserID() { - // If we've gotten here, that means this client may have previously used an old-style device ID. We don't know for sure, - // because it's possible that the user never used an old version of Lantern on this device. In either case, it's safe - // to request to migrate the device ID, as the server will know whether or not the old-style device ID was already associated - // with the current pro user. - oldStyleDeviceID := deviceid.OldStyleDeviceID() - if oldStyleDeviceID != app.settings.GetDeviceID() { - log.Debugf("Attempting to migrate device ID from %v to %v", oldStyleDeviceID, app.settings.GetDeviceID()) - err := pro.MigrateDeviceID(app.settings, oldStyleDeviceID) - if err != nil { - errString := err.Error() - if errString == "old-device-id-not-found" { - log.Debugf("Could not migrate device id, not fatal: %v", err) - } else { - return log.Errorf("Could not migrate device id: %v", err) - } - } else { - log.Debug("Successfully migrated device ID") - } - app.settings.SetMigratedDeviceIDForUserID(userID) - } - } - _, err := pro.FetchUserData(app.settings) + _, err := app.proClient.UserData(ctx) if err != nil { return errors.New("Could not get user data for %v: %v", userID, err) } @@ -97,7 +177,7 @@ func (app *App) servePro(channel ws.UIChannel) error { }() helloFn := func(write func(interface{})) { - if user, known := pro.GetUserDataFast(app.settings.GetUserID()); known { + if user, known := GetUserDataFast(ctx, app.settings.GetUserID()); known { log.Debugf("Sending current user data to new client: %v", user) write(user) } @@ -111,7 +191,7 @@ func (app *App) servePro(channel ws.UIChannel) error { if err != nil { return err } - pro.OnUserData(func(current *client.User, new *client.User) { + onUserData(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 diff --git a/desktop/lib.go b/desktop/lib.go index 7d58c84cf..d7b20c308 100644 --- a/desktop/lib.go +++ b/desktop/lib.go @@ -4,6 +4,8 @@ package main import ( "context" "encoding/json" + "fmt" + "net/http" "os" "os/signal" "path/filepath" @@ -17,21 +19,21 @@ import ( "github.com/getlantern/appdir" "github.com/getlantern/errors" "github.com/getlantern/flashlight/v7" - "github.com/getlantern/flashlight/v7/common" "github.com/getlantern/flashlight/v7/issue" "github.com/getlantern/flashlight/v7/logging" "github.com/getlantern/flashlight/v7/ops" - "github.com/getlantern/flashlight/v7/pro" - "github.com/getlantern/flashlight/v7/pro/client" + "github.com/getlantern/flashlight/v7/proxied" "github.com/getlantern/golog" "github.com/getlantern/i18n" "github.com/getlantern/jibber_jabber" "github.com/getlantern/lantern-client/desktop/app" "github.com/getlantern/lantern-client/desktop/autoupdate" + "github.com/getlantern/lantern-client/desktop/settings" + "github.com/getlantern/lantern-client/internalsdk/common" + proclient "github.com/getlantern/lantern-client/internalsdk/pro" "github.com/getlantern/lantern-client/internalsdk/protos" "github.com/getlantern/osversion" - "github.com/shirou/gopsutil/v3/host" "google.golang.org/protobuf/encoding/protojson" ) @@ -44,7 +46,7 @@ const ( var ( log = golog.LoggerFor("lantern-desktop.main") a *app.App - proClient *client.Client + proClient proclient.ProClient ) var issueMap = map[string]string{ @@ -70,7 +72,15 @@ func start() { cdir := configDir(&flags) settings := loadSettings(cdir) - proClient = pro.NewClient() + proClient = proclient.NewClient(fmt.Sprintf("https://%s", common.ProAPIHost), &proclient.Opts{ + HttpClient: &http.Client{ + Transport: proxied.ParallelForIdempotent(), + Timeout: 30 * time.Second, + }, + UserConfig: func() common.UserConfig { + return userConfig(settings) + }, + }) a = app.NewApp(flags, cdir, proClient, settings) @@ -118,11 +128,11 @@ func fetchOrCreate() error { settings := a.Settings() userID := settings.GetUserID() if userID == 0 { - user, err := pro.NewUser(settings) + resp, err := proClient.UserCreate(context.Background()) if err != nil { return errors.New("Could not create new Pro user: %v", err) } - settings.SetUserIDAndToken(user.Auth.ID, user.Auth.Token) + settings.SetUserIDAndToken(resp.User.UserId, resp.User.Token) } return nil } @@ -159,9 +169,9 @@ func setSelectTab(ttab *C.char) { //export plans func plans() *C.char { - resp, err := proClient.Plans(userConfig()) + resp, err := proClient.Plans(context.Background()) if err != nil { - return sendError(err) + return sendError(errors.New("error fetching plans: %v", err)) } b, _ := json.Marshal(resp.Plans) return C.CString(string(b)) @@ -169,16 +179,16 @@ func plans() *C.char { //export paymentMethods func paymentMethods() *C.char { - resp, err := proClient.PaymentMethods(userConfig()) + resp, err := proClient.PaymentMethods(context.Background()) if err != nil { - return sendError(err) + return sendError(errors.New("error fetching payment methods: %v", err)) } b, _ := json.Marshal(resp.Providers) return C.CString(string(b)) } -func getUserData() (*client.User, error) { - resp, err := proClient.UserData(userConfig()) +func getUserData() (*protos.User, error) { + resp, err := proClient.UserData(context.Background()) if err != nil { return nil, err } @@ -186,7 +196,7 @@ func getUserData() (*client.User, error) { if user.Email != "" { a.Settings().SetEmailAddress(user.Email) } - return &user, nil + return user, nil } //export devices @@ -215,7 +225,7 @@ func sendError(err error) *C.char { //export approveDevice func approveDevice(code *C.char) *C.char { - resp, err := proClient.LinkCodeApprove(userConfig(), C.GoString(code)) + resp, err := proClient.LinkCodeApprove(context.Background(), C.GoString(code)) if err != nil { return sendError(err) } @@ -224,7 +234,7 @@ func approveDevice(code *C.char) *C.char { //export removeDevice func removeDevice(deviceId *C.char) *C.char { - resp, err := proClient.DeviceRemove(userConfig(), C.GoString(deviceId)) + resp, err := proClient.DeviceRemove(context.Background(), C.GoString(deviceId)) if err != nil { log.Error(err) return sendError(err) @@ -275,9 +285,8 @@ func emailAddress() *C.char { //export emailExists func emailExists(email *C.char) *C.char { - err := proClient.EmailExists(userConfig(), C.GoString(email)) + _, err := proClient.EmailExists(context.Background(), C.GoString(email)) if err != nil { - log.Error(err) return sendError(err) } return C.CString("false") @@ -288,21 +297,31 @@ func emailExists(email *C.char) *C.char { // If an error occurs during redemption, the first string is nil, and the second string contains the error message. // //export redeemResellerCode -func redeemResellerCode(email, currency, deviceName, resellerCode *C.char) (*C.char, *C.char) { - _, err := proClient.RedeemResellerCode(userConfig(), C.GoString(email), C.GoString(resellerCode), - C.GoString(deviceName), C.GoString(currency)) +func redeemResellerCode(email, currency, deviceName, resellerCode *C.char) *C.char { + response, err := proClient.RedeemResellerCode(context.Background(), &protos.RedeemResellerCodeRequest{ + Currency: C.GoString(currency), + DeviceName: C.GoString(deviceName), + Email: C.GoString(email), + IdempotencyKey: strconv.FormatInt(time.Now().UnixMilli(), 10), + ResellerCode: C.GoString(resellerCode), + Provider: "reseller-code", + }) + log.Debugf("DEBUG: redeeming reseller code response: %v", response) + if response.Error != "" { + log.Debugf("DEBUG: error while redeeming reseller code reponse is: %v", response.Error) + return sendError(errors.New("Error while redeeming reseller code: %v", response.Error)) + } if err != nil { log.Debugf("DEBUG: error while redeeming reseller code: %v", err) - return nil, C.CString(err.Error()) - // return sendError(err) + return sendError(err) } - log.Debugf("DEBUG: redeeming reseller code success: %v", err) - return C.CString("true"), nil + log.Debug("DEBUG: redeeming reseller code success") + return C.CString("true") } //export referral func referral() *C.char { - referralCode, err := a.ReferralCode(userConfig()) + referralCode, err := a.ReferralCode(userConfig(a.Settings())) if err != nil { return sendError(err) } @@ -375,18 +394,24 @@ func acceptedTermsVersion() *C.char { //export proUser func proUser() *C.char { + ctx := context.Background() // refresh user data when home page is loaded on desktop - go pro.FetchUserData(a.Settings()) - if isProUser, ok := a.IsProUser(); isProUser && ok { + go proClient.UserData(ctx) + uc := a.Settings() + if isProUser, ok := app.IsProUserFast(ctx, uc); isProUser && ok { return C.CString("true") } return C.CString("false") } +func deviceName() string { + deviceName, _ := osversion.GetHumanReadable() + return deviceName +} + //export deviceLinkingCode func deviceLinkingCode() *C.char { - info, _ := host.Info() - resp, err := proClient.RequestDeviceLinkingCode(userConfig(), info.Hostname) + resp, err := proClient.LinkCodeRequest(context.Background(), deviceName()) if err != nil { return sendError(err) } @@ -396,7 +421,7 @@ func deviceLinkingCode() *C.char { //export paymentRedirect func paymentRedirect(planID, currency, provider, email, deviceName *C.char) *C.char { country := a.Settings().GetCountry() - resp, err := proClient.PaymentRedirect(userConfig(), &client.PaymentRedirectRequest{ + resp, err := proClient.PaymentRedirect(context.Background(), &protos.PaymentRedirectRequest{ Plan: C.GoString(planID), Provider: C.GoString(provider), Currency: strings.ToUpper(C.GoString(currency)), @@ -430,10 +455,9 @@ func replicaAddr() *C.char { return C.CString("") } -func userConfig() *common.UserConfigData { - settings := a.Settings() +func userConfig(settings *settings.Settings) common.UserConfig { userID, deviceID, token := settings.GetUserID(), settings.GetDeviceID(), settings.GetToken() - return common.NewUserConfigData( + return common.NewUserConfig( common.DefaultAppName, deviceID, userID, @@ -451,10 +475,11 @@ func reportIssue(email, issueType, description *C.char) (*C.char, *C.char) { if err != nil { return nil, sendError(err) } - uc := userConfig() + ctx := context.Background() + uc := userConfig(a.Settings()) subscriptionLevel := "free" - if isProUser, ok := a.IsProUser(); ok && isProUser { + if isProUser, ok := app.IsProUserFast(ctx, uc); ok && isProUser { subscriptionLevel = "pro" } @@ -503,30 +528,13 @@ func checkUpdates() *C.char { return C.CString(updateURL) } -//export purchase -func purchase(planID, email, cardNumber, expDate, cvc string) *C.char { - /*resp, err := proClient.Purchase(&proclient.PurchaseRequest{ - Provider: proclient.Provider_STRIPE, - Email: email, - Plan: planID, - CardNumber: cardNumber, - ExpDate: expDate, - Cvc: cvc, - }) - if err != nil { - return sendError(err) - } - b, _ := json.Marshal(resp)*/ - return C.CString("") -} - // loadSettings loads the initial settings at startup, either from disk or using defaults. -func loadSettings(configDir string) *app.Settings { +func loadSettings(configDir string) *settings.Settings { path := filepath.Join(configDir, "settings.yaml") if common.Staging { path = filepath.Join(configDir, "settings-staging.yaml") } - settings := app.LoadSettingsFrom(app.ApplicationVersion, app.RevisionDate, app.BuildDate, path) + settings := settings.LoadSettingsFrom(app.ApplicationVersion, app.RevisionDate, app.BuildDate, path) if common.Staging { settings.SetUserIDAndToken(9007199254740992, "OyzvkVvXk7OgOQcx-aZpK5uXx6gQl5i8BnOuUkc0fKpEZW6tc8uUvA") } diff --git a/desktop/app/settings.go b/desktop/settings/settings.go similarity index 93% rename from desktop/app/settings.go rename to desktop/settings/settings.go index a54030e7b..bc7212cc9 100644 --- a/desktop/app/settings.go +++ b/desktop/settings/settings.go @@ -1,4 +1,4 @@ -package app +package settings import ( "encoding/json" @@ -152,8 +152,8 @@ func LoadSettingsFrom(version, revisionDate, buildDate, path string) *Settings { return sett } -// emptySettings returns a new settings instance without loading any file from disk. -func emptySettings() *Settings { +// EmptySettings returns a new settings instance without loading any file from disk. +func EmptySettings() *Settings { return LoadSettingsFrom("version", "revisionDate", "buildDate", "") } @@ -420,6 +420,16 @@ func (s *Settings) SetProxyAll(proxyAll bool) { s.setVal(SNProxyAll, proxyAll) } +// GetDisconnected returns whether or not we're disconnected +func (s *Settings) GetDisconnected() bool { + return s.getBool(SNDisconnected) +} + +// SetDisconnected sets whether or not we're disconnected +func (s *Settings) SetDisconnected(disconnected bool) { + s.setBool(SNDisconnected, disconnected) +} + // GetGoogleAds returns whether or not to proxy all traffic. func (s *Settings) GetGoogleAds() bool { return s.getBool(SNGoogleAds) @@ -477,6 +487,16 @@ func (s *Settings) GetLocalHTTPToken() string { return s.getString(SNLocalHTTPToken) } +// GetPastAnnouncements returns past campaign announcements +func (s *Settings) GetPastAnnouncements() []string { + return s.getStringArray(SNPastAnnouncements) +} + +// SetPastAnnouncements sets past campaigns announcements +func (s *Settings) SetPastAnnouncements(announcements []string) { + s.setStringArray(SNPastAnnouncements, announcements) +} + // SetUIAddr sets the last known UI address. func (s *Settings) SetUIAddr(uiaddr string) { s.setVal(SNUIAddr, uiaddr) @@ -487,6 +507,21 @@ func (s *Settings) GetAddr() string { return s.getString(SNAddr) } +// SetAddr sets the HTTP proxy address. +func (s *Settings) SetAddr(addr string) { + s.setString(SNAddr, addr) +} + +// GetSOCKSAddr returns the SOCKS proxy address. +func (s *Settings) GetSOCKSAddr() string { + return s.getString(SNSOCKSAddr) +} + +// SetSOCKSAddr sets the SOCKS proxy address. +func (s *Settings) SetSOCKSAddr(addr string) { + s.setString(SNSOCKSAddr, addr) +} + // GetEmailAddress gets the email address of pro users. func (s *Settings) GetEmailAddress() string { return s.getString(SNEmailAddress) diff --git a/go.mod b/go.mod index 73f963836..2e47cf2ec 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/getlantern/lantern-client -go 1.21.4 +go 1.22.0 // replace github.com/getlantern/flashlight/v7 => ../flashlight @@ -29,15 +29,16 @@ replace github.com/google/netstack => github.com/getlantern/netstack v0.0.0-2022 replace github.com/eycorsican/go-tun2socks => github.com/getlantern/go-tun2socks v1.16.12-0.20201218023150-b68f09e5ae93 require ( + github.com/blang/semver v3.5.1+incompatible github.com/getlantern/appdir v0.0.0-20200615192800-a0ef1968f4da github.com/getlantern/autoupdate v0.0.0-20211217175350-d0b211f39ba7 github.com/getlantern/diagnostics v0.0.0-20230503185158-c2fc28ed22fe github.com/getlantern/dnsgrab v0.0.0-20240124035712-497ccf435858 - github.com/getlantern/errors v1.0.4 + github.com/getlantern/errors v1.0.5-0.20240410211607-f268a297d5d1 github.com/getlantern/eventual v1.0.0 github.com/getlantern/eventual/v2 v2.0.2 github.com/getlantern/filepersist v0.0.0-20210901195658-ed29a1cb0b7c - github.com/getlantern/flashlight/v7 v7.6.71 + github.com/getlantern/flashlight/v7 v7.6.73 github.com/getlantern/golog v0.0.0-20230503153817-8e72de7e0a65 github.com/getlantern/i18n v0.0.0-20181205222232-2afc4f49bb1c github.com/getlantern/idletiming v0.0.0-20231030193830-6767b09f86db @@ -48,7 +49,7 @@ require ( github.com/getlantern/mtime v0.0.0-20200417132445-23682092d1f7 github.com/getlantern/netx v0.0.0-20240124040039-163b1628a66b github.com/getlantern/notifier v0.0.0-20220715102006-f432f7e83f94 - github.com/getlantern/osversion v0.0.0-20230401075644-c2a30e73c451 + github.com/getlantern/osversion v0.0.0-20240418205916-2e84a4a4e175 github.com/getlantern/pathdb v0.0.0-20231026090702-54ee1ddd99eb github.com/getlantern/profiling v0.0.0-20160317154340-2a15afbadcff github.com/getlantern/replica v0.14.3 @@ -59,15 +60,18 @@ require ( github.com/getlantern/yaml v0.0.0-20190801163808-0c9bb1ebf426 github.com/getsentry/sentry-go v0.27.0 github.com/go-ping/ping v1.1.0 + github.com/go-resty/resty/v2 v2.12.0 github.com/google/uuid v1.6.0 github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.5.0 github.com/jackpal/gateway v1.0.13 + github.com/leekchan/accounting v1.0.0 github.com/shirou/gopsutil/v3 v3.24.1 + github.com/shopspring/decimal v1.4.0 github.com/stretchr/testify v1.8.4 - golang.org/x/mobile v0.0.0-20240112133503-c713f31d574b - golang.org/x/net v0.21.0 - golang.org/x/sys v0.17.0 + golang.org/x/mobile v0.0.0-20240404231514-09dbf07665ed + golang.org/x/net v0.24.0 + golang.org/x/sys v0.19.0 google.golang.org/protobuf v1.32.0 nhooyr.io/websocket v1.8.10 ) @@ -112,10 +116,10 @@ require ( github.com/benbjohnson/immutable v0.4.3 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.7.0 // indirect - github.com/blang/semver v3.5.1+incompatible // indirect github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cockroachdb/apd v1.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dchest/siphash v1.2.3 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect @@ -317,13 +321,13 @@ require ( go.uber.org/mock v0.4.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect - golang.org/x/crypto v0.19.0 // indirect + golang.org/x/crypto v0.22.0 // indirect golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect - golang.org/x/mod v0.14.0 // indirect - golang.org/x/sync v0.6.0 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/sync v0.7.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.17.0 // indirect + golang.org/x/tools v0.20.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe // indirect diff --git a/go.sum b/go.sum index 5a3157647..50fea5600 100644 --- a/go.sum +++ b/go.sum @@ -146,6 +146,8 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -264,8 +266,8 @@ github.com/getlantern/enhttp v0.0.0-20210901195634-6f89d45ee033 h1:HbjEpFFiRYcyS github.com/getlantern/enhttp v0.0.0-20210901195634-6f89d45ee033/go.mod h1:kHP/nfmHj9HJVN5Cb+1RFNRLR0O0nx40YENc4wKIe6s= github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7/go.mod h1:l+xpFBrCtDLpK9qNjxs+cHU6+BAdlBaxHqikB6Lku3A= github.com/getlantern/errors v1.0.1/go.mod h1:l+xpFBrCtDLpK9qNjxs+cHU6+BAdlBaxHqikB6Lku3A= -github.com/getlantern/errors v1.0.4 h1:i2iR1M9GKj4WuingpNqJ+XQEw6i6dnAgKAmLj6ZB3X0= -github.com/getlantern/errors v1.0.4/go.mod h1:/Foq8jtSDGP8GOXzAjeslsC4Ar/3kB+UiQH+WyV4pzY= +github.com/getlantern/errors v1.0.5-0.20240410211607-f268a297d5d1 h1:06/WReVGjGazEKDQcT/OADWkhr/EQY3Q8TdtLCZfu5E= +github.com/getlantern/errors v1.0.5-0.20240410211607-f268a297d5d1/go.mod h1:L1h+WK2Enz9MzoHsyGpdtQlwXe7U/ANbSM4rEbAl3N8= github.com/getlantern/event v0.0.0-20210901195647-a7e3145142e6 h1:sjFsoQHJqzDiwgbOLHnG/zYIpN1Sbmv/7gk1ie/KkHg= github.com/getlantern/event v0.0.0-20210901195647-a7e3145142e6/go.mod h1:iToZ3dqm/iFxRHPHUHUrF1JZtg0e06ZSXD1BuiGoUaY= github.com/getlantern/eventual v0.0.0-20180125201821-84b02499361b/go.mod h1:O8T3zFEcY6+LRXFcVV4q8mEu2tDIixG8edC84DfswBc= @@ -280,9 +282,16 @@ github.com/getlantern/fdcount v0.0.0-20210503151800-5decd65b3731/go.mod h1:XZwE+ github.com/getlantern/filepersist v0.0.0-20160317154340-c5f0cd24e799/go.mod h1:8DGAx0LNUfXNnEH+fXI0s3OCBA/351kZCiz/8YSK3i8= github.com/getlantern/filepersist v0.0.0-20210901195658-ed29a1cb0b7c h1:mcz27xtAkb1OuOLBct/uFfL1p3XxAIcFct82GbT+UZM= github.com/getlantern/filepersist v0.0.0-20210901195658-ed29a1cb0b7c/go.mod h1:8DGAx0LNUfXNnEH+fXI0s3OCBA/351kZCiz/8YSK3i8= -github.com/getlantern/flashlight v0.0.0-20230403092335-5f84ae10c585 h1:lVu4HyDAxXkX4kJEJQWpJJz+Poprb0crUGW5zhoh3GU= -github.com/getlantern/flashlight/v7 v7.6.71 h1:/IYezK1+PSClDC9V/3JgNs9UqkHP96h+CiR9JkMlls0= -github.com/getlantern/flashlight/v7 v7.6.71/go.mod h1:n5EO3xn22j9uzRxyalP0G9TticdfWD26r6JEkfkSShE= +github.com/getlantern/flashlight/v7 v7.6.72 h1:iGTCx9HRvuMn8wrZzkvvs/pk2+1LEAgfF7QJUz7IHfA= +github.com/getlantern/flashlight/v7 v7.6.72/go.mod h1:n5EO3xn22j9uzRxyalP0G9TticdfWD26r6JEkfkSShE= +github.com/getlantern/flashlight/v7 v7.6.73-0.20240418015217-aef9d0840c2c h1:xHCy5MFV28iM/2LH2e6NCDB4WrT/x7xXoCjqQ95RnpU= +github.com/getlantern/flashlight/v7 v7.6.73-0.20240418015217-aef9d0840c2c/go.mod h1:2cmXfzBC2Aae6ZAFB0F4MlTcbm5S1OquZzIC05BsC6g= +github.com/getlantern/flashlight/v7 v7.6.73-0.20240418215636-1476667f94fd h1:cjgwd5yaa7IRzOZr+l8PuPyEl59byHNrLnQZg6Lhdcw= +github.com/getlantern/flashlight/v7 v7.6.73-0.20240418215636-1476667f94fd/go.mod h1:n5EO3xn22j9uzRxyalP0G9TticdfWD26r6JEkfkSShE= +github.com/getlantern/flashlight/v7 v7.6.73-0.20240418220025-51093a140a26 h1:rEUbTZluIrcBU2Ui21rn8dtDzr9gTTrFQB+FR2PcLXk= +github.com/getlantern/flashlight/v7 v7.6.73-0.20240418220025-51093a140a26/go.mod h1:n5EO3xn22j9uzRxyalP0G9TticdfWD26r6JEkfkSShE= +github.com/getlantern/flashlight/v7 v7.6.73 h1:KC//uhGpLmxp+vMBf0I3/eaCzlPNdcNI9cqF4cWs/gk= +github.com/getlantern/flashlight/v7 v7.6.73/go.mod h1:n5EO3xn22j9uzRxyalP0G9TticdfWD26r6JEkfkSShE= github.com/getlantern/framed v0.0.0-20190601192238-ceb6431eeede h1:yrU6Px3ZkvCsDLPryPGi6FN+2iqFPq+JeCb7EFoDBhw= github.com/getlantern/framed v0.0.0-20190601192238-ceb6431eeede/go.mod h1:nhnoiS6DE6zfe+BaCMU4YI01UpsuiXnDqM5S8jxHuuI= github.com/getlantern/fronted v0.0.0-20230601004823-7fec719639d8 h1:r/Z/SPPIfLXDI3QA7/tE6nOfPncrqeUPDjiFjnNGP50= @@ -381,11 +390,10 @@ github.com/getlantern/notifier v0.0.0-20220715102006-f432f7e83f94 h1:cFjb6KVuCqS github.com/getlantern/notifier v0.0.0-20220715102006-f432f7e83f94/go.mod h1:ohU+3pliOCY7Kirkbdzbe6mhBJj/gjx8nKHCD6l9GOo= github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA= github.com/getlantern/ops v0.0.0-20200403153110-8476b16edcd6/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA= -github.com/getlantern/ops v0.0.0-20220713155959-1315d978fff7/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA= github.com/getlantern/ops v0.0.0-20231025133620-f368ab734534 h1:3BwvWj0JZzFEvNNiMhCu4bf60nqcIuQpTYb00Ezm1ag= github.com/getlantern/ops v0.0.0-20231025133620-f368ab734534/go.mod h1:ZsLfOY6gKQOTyEcPYNA9ws5/XHZQFroxqCOhHjGcs9Y= -github.com/getlantern/osversion v0.0.0-20230401075644-c2a30e73c451 h1:3Nn0AqIlImbb0TE4oFhr4BETBZhRUPo9GuebHvMzWB4= -github.com/getlantern/osversion v0.0.0-20230401075644-c2a30e73c451/go.mod h1:kaUdXyKE1Y8bwPnlN7ChFXWnkADpL0zZrk8F0XbpKcc= +github.com/getlantern/osversion v0.0.0-20240418205916-2e84a4a4e175 h1:JWH5BB2o0eAeGs0tZnFPpQGx+nMIo/WmxKnj2hnGjgE= +github.com/getlantern/osversion v0.0.0-20240418205916-2e84a4a4e175/go.mod h1:h3S9LBmmzN/xM+lwYZHE4abzTtCTtidKtG+nxZcCZX0= github.com/getlantern/packetforward v0.0.0-20201001150407-c68a447b0360 h1:pijUoofaQcAM/8zbDzZM2LQ90kGVbKfnSAkFnQwLZZU= github.com/getlantern/packetforward v0.0.0-20201001150407-c68a447b0360/go.mod h1:nsJPNYUSY96xB+p7uiDW8O4uiKea+KjeUdS5d6tf9IU= github.com/getlantern/pathdb v0.0.0-20231026090702-54ee1ddd99eb h1:rbkflUPjr83ON7c+4LqU2Y/rJzv9P/fmocXDLLm0hWQ= @@ -476,7 +484,6 @@ github.com/go-llsqlite/crawshaw v0.5.1/go.mod h1:/YJdV7uBQaYDE0fwe4z3wwJIZBJxdYz github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -487,6 +494,8 @@ github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw= github.com/go-ping/ping v1.1.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= +github.com/go-resty/resty/v2 v2.12.0 h1:rsVL8P90LFvkUYq/V5BTVe203WfRIU4gvcf+yfzJzGA= +github.com/go-resty/resty/v2 v2.12.0/go.mod h1:o0yGPrkS3lOe1+eFajk6kBW8ScXzwU3hD69/gt2yB/0= github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= @@ -546,7 +555,6 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -643,6 +651,10 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leekchan/accounting v1.0.0 h1:+Wd7dJ//dFPa28rc1hjyy+qzCbXPMR91Fb6F1VGTQHg= +github.com/leekchan/accounting v1.0.0/go.mod h1:3timm6YPhY3YDaGxl0q3eaflX0eoSx3FXn7ckHe4tO0= +github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM= github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= @@ -717,8 +729,6 @@ github.com/oschwald/maxminddb-golang v1.11.0 h1:aSXMqYR/EPNjGE8epgqwDay+P30hCBZI github.com/oschwald/maxminddb-golang v1.11.0/go.mod h1:YmVI+H0zh3ySFR3w+oz8PCfglAFj3PuCmui13+P9zDg= github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw= github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= -github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= -github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.0.1-0.20170904195809-1d6b12b7cb29/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= @@ -861,6 +871,9 @@ github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFt github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 h1:xT+JlYxNGqyT+XcU8iUrN18JYed2TvG9yN5ULG2jATM= github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -974,7 +987,6 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.0 h1:pginetY7+onl4qN1vl0xW/V/v6OBZ0vVdH+esuJgvmM= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.0/go.mod h1:XiYsayHc36K3EByOO6nbAXnAWbrUxdjUROCEeeROOH8= -go.opentelemetry.io/otel v1.9.0/go.mod h1:np4EoPGzoPs3O67xUVNoPPcmSvsfOxNlNA4F4AC+0Eo= go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y= go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.42.0 h1:ZtfnDL+tUrs1F0Pzfwbg2d59Gru9NCH3bgSHBM6LDwU= @@ -991,7 +1003,6 @@ go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZ go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= go.opentelemetry.io/otel/sdk/metric v1.19.0 h1:EJoTO5qysMsYCa+w4UghwFV/ptQgqSL/8Ni+hx+8i1k= go.opentelemetry.io/otel/sdk/metric v1.19.0/go.mod h1:XjG0jQyFJrv2PbMvwND7LwCEhsJzCzV5210euduKcKY= -go.opentelemetry.io/otel/trace v1.9.0/go.mod h1:2737Q0MuG8q1uILYm2YYVkAyLtOofiTNGg6VODnOiPo= go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0= go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= @@ -1030,8 +1041,10 @@ golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM= golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= @@ -1041,15 +1054,15 @@ golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTk golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20240112133503-c713f31d574b h1:kfWLZgb8iUBHdE9WydD5V5dHIS/F6HjlBZNyJfn2bs4= -golang.org/x/mobile v0.0.0-20240112133503-c713f31d574b/go.mod h1:4efzQnuA1nICq6h4kmZRMGzbPiP06lZvgADUu1VpJCE= +golang.org/x/mobile v0.0.0-20240404231514-09dbf07665ed h1:vZhAhVr5zF1IJaVKTawyTq78WSspLnK53iuMJ1fJgLc= +golang.org/x/mobile v0.0.0-20240404231514-09dbf07665ed/go.mod h1:z041I2NhLjANgIfD0XbB2AmUZ8sLUcSgyLaSNGEP50M= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1090,8 +1103,10 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/oauth2 v0.0.0-20170912212905-13449ad91cb2/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1106,8 +1121,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1158,8 +1173,10 @@ golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1170,6 +1187,8 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -1200,8 +1219,8 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= -golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= +golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internalsdk/apimodels/api.go b/internalsdk/apimodels/api.go deleted file mode 100644 index ca0439e5c..000000000 --- a/internalsdk/apimodels/api.go +++ /dev/null @@ -1,97 +0,0 @@ -package apimodels - -import ( - "bytes" - "encoding/json" - "net/http" - "time" - - "github.com/getlantern/flashlight/v7/proxied" - "github.com/getlantern/golog" -) - -const ( - baseUrl = "https://api.getiantem.org" - userDetailUrl = baseUrl + "/user-data" - userCreateUrl = baseUrl + "/user-create" -) - -var ( - log = golog.LoggerFor("lantern-internalsdk-http") - httpClient = &http.Client{ - Transport: proxied.ParallelForIdempotent(), - Timeout: 30 * time.Second, - } -) - -func FechUserDetail(deviceId string, userId string, token string) (*UserDetailResponse, error) { - // Create a new request - req, err := http.NewRequest("GET", userDetailUrl, nil) - if err != nil { - log.Errorf("Error creating user details request: %v", err) - return nil, err - } - - // Add headers - req.Header.Set("X-Lantern-Device-Id", deviceId) - req.Header.Set("X-Lantern-User-Id", userId) - req.Header.Set("X-Lantern-Pro-Token", token) - log.Debugf("Headers set") - - // Send the request - resp, err := httpClient.Do(req) - if err != nil { - log.Errorf("Error sending user details request: %v", err) - return nil, err - } - defer resp.Body.Close() - - // Read the response body - var userDetail UserDetailResponse - // Read and decode the response body - if err := json.NewDecoder(resp.Body).Decode(&userDetail); err != nil { - log.Errorf("Error decoding response body: %v", err) - return nil, err - } - - return &userDetail, nil -} - -func UserCreate(deviceId string, local string) (*UserResponse, error) { - requestBodyMap := map[string]string{ - "locale": local, - } - - // Marshal the map to JSON - requestBody, err := json.Marshal(requestBodyMap) - if err != nil { - log.Errorf("Error marshaling request body: %v", err) - return nil, err - } - - // Create a new request - req, err := http.NewRequest("POST", userCreateUrl, bytes.NewBuffer(requestBody)) - if err != nil { - log.Errorf("Error creating new request: %v", err) - return nil, err - } - - // Add headers - req.Header.Set("X-Lantern-Device-Id", deviceId) - log.Debugf("Headers set") - - // Send the request - resp, err := httpClient.Do(req) - if err != nil { - log.Errorf("Error sending request: %v", err) - return nil, err - } - defer resp.Body.Close() - var userResponse UserResponse - // Read and decode the response body - if err := json.NewDecoder(resp.Body).Decode(&userResponse); err != nil { - log.Errorf("Error decoding response body: %v", err) - return nil, err - } - return &userResponse, nil -} diff --git a/internalsdk/apimodels/json_models.go b/internalsdk/apimodels/json_models.go deleted file mode 100644 index 659764345..000000000 --- a/internalsdk/apimodels/json_models.go +++ /dev/null @@ -1,44 +0,0 @@ -package apimodels - -type UserResponse struct { - UserID float64 `json:"userId"` - Code string `json:"code"` - Token string `json:"token"` - Referral string `json:"referral"` - Locale string `json:"locale"` - Servers []string `json:"servers"` - Inviters []string `json:"inviters"` - Invitees []string `json:"invitees"` - Devices []string `json:"devices"` - YinbiEnabled bool `json:"yinbiEnabled"` -} - -type UserDetailResponse struct { - UserID int64 `json:"userId"` - Code string `json:"code"` - Token string `json:"token"` - Referral string `json:"referral"` - Email string `json:"email"` - UserStatus string `json:"userStatus"` - UserLevel string `json:"userLevel"` - Locale string `json:"locale"` - Expiration int64 `json:"expiration"` - Servers []string `json:"servers"` - Purchases []Purchase `json:"purchases"` - BonusDays string `json:"bonusDays"` - BonusMonths string `json:"bonusMonths"` - Inviters []string `json:"inviters"` - Invitees []string `json:"invitees"` - Devices []UserDevice `json:"devices"` - YinbiEnabled bool `json:"yinbiEnabled"` -} - -type Purchase struct { - Plan string `json:"plan"` -} - -type UserDevice struct { - ID string `json:"id"` - Name string `json:"name"` - Created int64 `json:"created"` -} diff --git a/internalsdk/common/const.go b/internalsdk/common/const.go new file mode 100644 index 000000000..e386aa34e --- /dev/null +++ b/internalsdk/common/const.go @@ -0,0 +1,88 @@ +package common + +import ( + "os" + "strconv" + "time" + + "github.com/getlantern/golog" +) + +const ( + // The following are over HTTP because proxies do not forward X-Forwarded-For + // with HTTPS and because we only support falling back to direct domain + // fronting through the local proxy for HTTP. + + // ProxiesURL is the URL for fetching the per user proxy config. + ProxiesURL = "http://config.getiantem.org/proxies.yaml.gz" + + // ProxiesStagingURL is the URL for fetching the per user proxy config in a staging environment. + ProxiesStagingURL = "http://config-staging.getiantem.org/proxies.yaml.gz" + + // Sentry Configurations + SentryTimeout = time.Second * 30 + SentryMaxMessageChars = 8000 + + // UpdateServerURL is the URL of the update server. Different applications + // hit the server on separate paths "/update/". + UpdateServerURL = "https://update.getlantern.org/" +) + +var ( + EnvironmentDevelopment = "development" + EnvironmentProduction = "production" +) + +var ( + // GlobalURL URL for fetching the global config. + GlobalURL = "https://globalconfig.flashlightproxy.com/global.yaml.gz" + + // GlobalStagingURL is the URL for fetching the global config in a staging environment. + GlobalStagingURL = "https://globalconfig.flashlightproxy.com/global.yaml.gz" + + // StagingMode if true, run Lantern against our staging infrastructure. + // This is set by the linker using -ldflags + StagingMode = "false" + + Staging = false + + ProAPIHost = "api.getiantem.org" + + log = golog.LoggerFor("flashlight.common") + + forceAds bool + + // Set by the linker using -ldflags in the project's Makefile. + // Defaults to 'production' so as not to mistakingly push development work + // to a production environment + Environment = "production" +) + +func init() { + initInternal() +} + +// ForceStaging forces staging mode. +func ForceStaging() { + StagingMode = "true" + initInternal() +} + +func initInternal() { + var err error + log.Debugf("****************************** stagingMode: %v", StagingMode) + Staging, err = strconv.ParseBool(StagingMode) + if err != nil { + log.Errorf("Error parsing boolean flag: %v", err) + return + } + if Staging { + ProAPIHost = "api-staging.getiantem.org" + } + forceAds, _ = strconv.ParseBool(os.Getenv("FORCEADS")) +} + +// ForceAds indicates whether adswapping should be forced to 100% +func ForceAds() bool { + return forceAds +} diff --git a/internalsdk/common/headers.go b/internalsdk/common/headers.go new file mode 100644 index 000000000..11a73286f --- /dev/null +++ b/internalsdk/common/headers.go @@ -0,0 +1,168 @@ +package common + +import ( + "crypto/rand" + "math/big" + "net/http" + "strconv" + "strings" + + mrand "math/rand" +) + +const ( + AppHeader = "X-Lantern-App" + LibraryVersionHeader = "X-Lantern-Version" + AppVersionHeader = "X-Lantern-App-Version" + DeviceIdHeader = "X-Lantern-Device-Id" + SupportedDataCapsHeader = "X-Lantern-Supported-Data-Caps" + TimeZoneHeader = "X-Lantern-Time-Zone" + TokenHeader = "X-Lantern-Auth-Token" + UserIdHeader = "X-Lantern-User-Id" + ProTokenHeader = "X-Lantern-Pro-Token" + CfgSvrAuthTokenHeader = "X-Lantern-Config-Auth-Token" + CfgSvrClientIPHeader = "X-Lantern-Config-Client-IP" + BBRBytesSentHeader = "X-BBR-Sent" + BBRAvailableBandwidthEstimateHeader = "X-BBR-ABE" + EtagHeader = "X-Lantern-Etag" + KernelArchHeader = "X-Lantern-KernelArch" + IfNoneMatchHeader = "X-Lantern-If-None-Match" + PingHeader = "X-Lantern-Ping" + PlatformHeader = "X-Lantern-Platform" + PlatformVersionHeader = "X-Lantern-PlatVer" + ClientCountryHeader = "X-Lantern-Client-Country" + RandomNoiseHeader = "X-Lantern-Rand" + SleepHeader = "X-Lantern-Sleep" + LocaleHeader = "X-Lantern-Locale" + XBQHeader = "XBQ" + XBQHeaderv2 = "XBQv2" +) + +var ( + // List of methods the client is allowed to use with cross-domain requests + corsAllowedMethods = []string{http.MethodGet, http.MethodPost, http.MethodOptions} +) + +// AddCommonNonUserHeaders adds all common headers that are not +// user or device specific. +func AddCommonNonUserHeaders(uc UserConfig, req *http.Request) { + req.Header.Set(AppVersionHeader, CompileTimeApplicationVersion) + req.Header.Set(LibraryVersionHeader, LibraryVersion) + for k, v := range uc.GetInternalHeaders() { + if v != "" { + req.Header.Set(k, v) + } + } + if len(uc.GetEnabledExperiments()) > 0 { + req.Header.Set("x-lantern-dev-experiments", strings.Join(uc.GetEnabledExperiments(), ",")) + } + + req.Header.Set(PlatformHeader, Platform) + req.Header.Set(AppHeader, uc.GetAppName()) + req.Header.Add(SupportedDataCapsHeader, "monthly") + req.Header.Add(SupportedDataCapsHeader, "weekly") + req.Header.Add(SupportedDataCapsHeader, "daily") + tz, err := uc.GetTimeZone() + if err != nil { + log.Debugf("omitting timezone header because: %v", err) + } else { + req.Header.Set(TimeZoneHeader, tz) + } + // We include a random length string here to make it harder for censors to identify lantern + // based on consistent packet lengths. + req.Header.Add(RandomNoiseHeader, randomizedString()) +} + +// AddCommonHeadersWithOptions sets standard http headers on a request bound +// for an internal service, representing auth and other configuration +// metadata. The caller may specify overwriteAuth=false to prevent overwriting +// any of the common 'auth' headers (DeviceIdHeader, ProTokenHeader, UserIdHeader) +// that are already present in the given request. +func AddCommonHeadersWithOptions(uc UserConfig, req *http.Request, overwriteAuth bool) { + AddCommonNonUserHeaders(uc, req) + if overwriteAuth || req.Header.Get(DeviceIdHeader) == "" { + if deviceID := uc.GetDeviceID(); deviceID != "" { + req.Header.Set(DeviceIdHeader, deviceID) + } + } + if overwriteAuth || req.Header.Get(ProTokenHeader) == "" { + if token := uc.GetToken(); token != "" { + req.Header.Set(ProTokenHeader, token) + } + } + if overwriteAuth || req.Header.Get(UserIdHeader) == "" { + if userID := uc.GetUserID(); userID != 0 { + req.Header.Set(UserIdHeader, strconv.FormatInt(userID, 10)) + } + } + req.Header.Set(LocaleHeader, uc.GetLanguage()) +} + +// AddCommonHeaders sets standard http headers on a request +// bound for an internal service, representing auth and other +// configuration metadata. +func AddCommonHeaders(uc UserConfig, req *http.Request) { + AddCommonHeadersWithOptions(uc, req, true) +} + +// isOriginAllowed checks if the origin is authorized +// for CORS requests. The origin can have include arbitrary +// ports, so we just make sure it's on localhost. +func isOriginAllowed(origin string) bool { + return strings.HasPrefix(origin, "http://localhost:") || + strings.HasPrefix(origin, "http://127.0.0.1:") || + strings.HasPrefix(origin, "http://[::1]:") +} + +// ProcessCORS processes CORS requests on localhost. +// It returns true if the request is a valid CORS request +// from an allowed origin and false otherwise. +func ProcessCORS(responseHeaders http.Header, r *http.Request) bool { + origin := r.Header.Get("origin") + if origin == "" { + log.Debugf("Request is not a CORS request") + return false + } + // The origin can have include arbitrary ports, so we just make sure + // it's on localhost. + if isOriginAllowed(origin) { + + responseHeaders.Set("Access-Control-Allow-Origin", origin) + responseHeaders.Set("Vary", "Origin") + responseHeaders.Set("Access-Control-Allow-Credentials", "true") + for _, method := range corsAllowedMethods { + responseHeaders.Add("Access-Control-Allow-Methods", method) + } + responseHeaders.Set("Access-Control-Allow-Headers", r.Header.Get("Access-Control-Request-Headers")) + return true + } + return false +} + +// CORSMiddleware is HTTP middleware used to process CORS requests on localhost +func CORSMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + if ok := ProcessCORS(w.Header(), req); ok && req.Method == "OPTIONS" { + // respond 200 OK to initial CORS request + w.WriteHeader(http.StatusOK) + return + } + next.ServeHTTP(w, req) + }) +} + +// randomizedString returns a random string to avoid consistent packet lengths censors +// may use to detect Lantern. +func randomizedString() string { + const charset = "abcdefghijklmnopqrstuvwxyz" + size, err := rand.Int(rand.Reader, big.NewInt(300)) + if err != nil { + return "" + } + + bytes := make([]byte, size.Int64()) + for i := range bytes { + bytes[i] = charset[mrand.Intn(len(charset))] + } + return string(bytes) +} diff --git a/internalsdk/common/headers_test.go b/internalsdk/common/headers_test.go new file mode 100644 index 000000000..97329fc0f --- /dev/null +++ b/internalsdk/common/headers_test.go @@ -0,0 +1,136 @@ +package common + +import ( + "fmt" + "net/http" + "net/http/httptest" + "sort" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +var allHeaders = []string{ + "Vary", + "Access-Control-Allow-Origin", + "Access-Control-Allow-Methods", + "Access-Control-Allow-Headers", + "Access-Control-Allow-Credentials", + "Access-Control-Max-Age", + "Access-Control-Expose-Headers", +} + +type CorsSpec struct { + name string + method string + uri string + reqHeaders map[string]string + respHeaders map[string]string +} + +var testHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("test")) +}) + +func assertHeaders(t *testing.T, resHeaders http.Header, expHeaders map[string]string) { + for _, name := range allHeaders { + sort.Strings(resHeaders[name]) + got := strings.Join(resHeaders[name], ", ") + want := expHeaders[name] + if got != want { + t.Errorf("Response header %q = %q, want %q", name, got, want) + } + } +} + +func TestCORSMiddleware(t *testing.T) { + uiAddr := "http://localhost:2000" + cases := []CorsSpec{ + { + "BadOrigin", + http.MethodGet, + "/login", + map[string]string{ + "Origin": "http://baddomain.com", + }, + map[string]string{ + "Vary": "", + "Access-Control-Allow-Origin": "", + }, + }, + { + "AllowedOrigin", + http.MethodGet, + "/login", + map[string]string{ + "Origin": uiAddr, + }, + map[string]string{ + "Vary": "Origin", + "Access-Control-Allow-Methods": strings.Join([]string{"GET", "OPTIONS", "POST"}, ", "), + "Access-Control-Allow-Credentials": "true", + "Access-Control-Allow-Origin": uiAddr, + }, + }, + } + for i := range cases { + tc := cases[i] + t.Run(tc.name, func(t *testing.T) { + url := fmt.Sprintf("http://localhost%s", tc.uri) + req, _ := http.NewRequest(tc.method, url, + nil) + for name, value := range tc.reqHeaders { + req.Header.Add(name, value) + } + resp := httptest.NewRecorder() + CORSMiddleware(testHandler).ServeHTTP(resp, req) + assertHeaders(t, resp.Header(), tc.respHeaders) + }) + } +} + +func TestProcessCORS(t *testing.T) { + + // CORS headers in response to localhost origin + resp := &http.Response{Header: http.Header{}} + r, err := http.NewRequest(http.MethodGet, "http://test.com", nil) + assert.NoError(t, err) + origin := "http://localhost:47899" + r.Header.Set("Origin", origin) + ProcessCORS(resp.Header, r) + + assert.Equal(t, origin, resp.Header.Get("Access-Control-Allow-Origin")) + methods := resp.Header.Values("Access-Control-Allow-Methods") + sort.Strings(methods) + assert.EqualValues(t, []string{"GET", "OPTIONS", "POST"}, methods) + + // CORS headers in response to 127.0.0.1 origin + resp = &http.Response{Header: http.Header{}} + r, err = http.NewRequest(http.MethodGet, "http://test.com", nil) + assert.NoError(t, err) + origin = "http://127.0.0.1:47899" + r.Header.Set("Origin", origin) + ProcessCORS(resp.Header, r) + + assert.Equal(t, origin, resp.Header.Get("Access-Control-Allow-Origin")) + methods = resp.Header.Values("Access-Control-Allow-Methods") + sort.Strings(methods) + assert.Equal(t, []string{"GET", "OPTIONS", "POST"}, methods) + + // No CORS headers in response to public origin + resp = &http.Response{Header: http.Header{}} + r, err = http.NewRequest(http.MethodGet, "http://test.com", nil) + assert.NoError(t, err) + origin = "http://publicdomain:47899" + r.Header.Set("Origin", origin) + ProcessCORS(resp.Header, r) + assert.Equal(t, "", resp.Header.Get("Access-Control-Allow-Origin")) + + // No CORS headers in response to no origin + resp = &http.Response{Header: http.Header{}} + r, err = http.NewRequest(http.MethodGet, "http://test.com", nil) + assert.NoError(t, err) + ProcessCORS(resp.Header, r) + assert.Equal(t, "", resp.Header.Get("Access-Control-Allow-Origin")) +} diff --git a/internalsdk/common/lantern_config.go b/internalsdk/common/lantern_config.go new file mode 100644 index 000000000..e4d0f530a --- /dev/null +++ b/internalsdk/common/lantern_config.go @@ -0,0 +1,15 @@ +package common + +const ( + // The default name for this app (used if no client-supplied name is passed at initialization) + DefaultAppName = "Lantern" + + // ProAvailable specifies whether the user can purchase pro with this version. + ProAvailable = true + + // TrackingID is the Google Analytics tracking ID. + TrackingID = "UA-21815217-12" + + // SentryDSN is Sentry's project ID thing + SentryDSN = "https://f65aa492b9524df79b05333a0b0924c5@sentry.io/2222244" +) diff --git a/internalsdk/common/platform.go b/internalsdk/common/platform.go new file mode 100644 index 000000000..ec8426b70 --- /dev/null +++ b/internalsdk/common/platform.go @@ -0,0 +1,7 @@ +//go:build !ios + +package common + +import "runtime" + +const Platform = runtime.GOOS diff --git a/internalsdk/common/platform_ios.go b/internalsdk/common/platform_ios.go new file mode 100644 index 000000000..799e28068 --- /dev/null +++ b/internalsdk/common/platform_ios.go @@ -0,0 +1,3 @@ +package common + +const Platform = "ios" diff --git a/internalsdk/common/user_config.go b/internalsdk/common/user_config.go new file mode 100644 index 000000000..1186ecaa9 --- /dev/null +++ b/internalsdk/common/user_config.go @@ -0,0 +1,62 @@ +package common + +import ( + "time" + + "github.com/getlantern/timezone" +) + +type UserConfig interface { + GetAppName() string + GetDeviceID() string + GetUserID() int64 + GetToken() string + GetLanguage() string + GetTimeZone() (string, error) + GetInternalHeaders() map[string]string + GetEnabledExperiments() []string +} + +// an implementation of UserConfig +type UserConfigData struct { + AppName string + DeviceID string + UserID int64 + Token string + Language string + Headers map[string]string +} + +func (uc *UserConfigData) GetAppName() string { return uc.AppName } +func (uc *UserConfigData) GetDeviceID() string { return uc.DeviceID } +func (uc *UserConfigData) GetUserID() int64 { return uc.UserID } +func (uc *UserConfigData) GetToken() string { return uc.Token } +func (uc *UserConfigData) GetLanguage() string { return uc.Language } +func (uc *UserConfigData) GetTimeZone() (string, error) { return timezone.IANANameForTime(time.Now()) } +func (uc *UserConfigData) GetEnabledExperiments() []string { return nil } +func (uc *UserConfigData) GetInternalHeaders() map[string]string { + h := make(map[string]string) + for k, v := range uc.Headers { + h[k] = v + } + return h +} + +var _ UserConfig = (*UserConfigData)(nil) + +// NewUserConfig constucts a new UserConfigData with the given options. +func NewUserConfig(appName string, deviceID string, userID int64, token string, headers map[string]string, + lang string) *UserConfigData { + uc := &UserConfigData{ + AppName: appName, + DeviceID: deviceID, + UserID: userID, + Token: token, + Language: lang, + Headers: make(map[string]string), + } + for k, v := range headers { + uc.Headers[k] = v + } + return uc +} diff --git a/internalsdk/common/version.go b/internalsdk/common/version.go new file mode 100644 index 000000000..ad992395f --- /dev/null +++ b/internalsdk/common/version.go @@ -0,0 +1,37 @@ +package common + +import ( + "runtime/debug" + "strings" + + "github.com/blang/semver" +) + +var ( + // CompileTimeApplicationVersion is set at compile-time by application production builds + CompileTimeApplicationVersion string = "" + + // LibraryVersion is determined at runtime based on the version of the lantern library that's been included. + LibraryVersion = "" +) + +func init() { + buildInfo, ok := debug.ReadBuildInfo() + if !ok { + panic("Unable to read build info") + } + +versionLoop: + for _, dep := range buildInfo.Deps { + if strings.HasPrefix(dep.Path, "github.com/getlantern/lantern-client") && strings.HasPrefix(dep.Version, "v") { + version := dep.Version[1:] + log.Debugf("Flashlight version is %v", version) + _, parseErr := semver.Parse(version) + if parseErr == nil { + log.Debugf("Setting LibraryVersion to %v", version) + LibraryVersion = version + break versionLoop + } + } + } +} diff --git a/internalsdk/pro/pro.go b/internalsdk/pro/pro.go new file mode 100644 index 000000000..1fe5f5975 --- /dev/null +++ b/internalsdk/pro/pro.go @@ -0,0 +1,244 @@ +package pro + +import ( + "context" + "encoding/json" + "net/http" + "strconv" + "strings" + + "github.com/getlantern/errors" + "github.com/getlantern/golog" + "github.com/getlantern/lantern-client/internalsdk/common" + "github.com/getlantern/lantern-client/internalsdk/pro/webclient" + "github.com/getlantern/lantern-client/internalsdk/pro/webclient/defaultwebclient" + "github.com/getlantern/lantern-client/internalsdk/protos" + + "github.com/go-resty/resty/v2" + "github.com/leekchan/accounting" + "github.com/shopspring/decimal" + "google.golang.org/protobuf/encoding/protojson" +) + +var ( + log = golog.LoggerFor("webclient") + errMissingDeviceName = errors.New("Missing device name") +) + +type proClient struct { + userConfig func() common.UserConfig + webclient webclient.RESTClient +} + +type Opts struct { + // HttpClient represents an http.Client that should be used by the resty client + HttpClient *http.Client + // UserConfig is a function that returns the user config associated with the Lantern user + UserConfig func() common.UserConfig +} + +type ProClient interface { + DeviceRemove(ctx context.Context, deviceId string) (*LinkResponse, error) + EmailExists(ctx context.Context, email string) (*protos.BaseResponse, error) + LinkCodeApprove(ctx context.Context, code string) (*protos.BaseResponse, error) + LinkCodeRequest(ctx context.Context, deviceName string) (*LinkCodeResponse, error) + PaymentMethods(ctx context.Context) (*PaymentMethodsResponse, error) + PaymentRedirect(ctx context.Context, req *protos.PaymentRedirectRequest) (*PaymentRedirectResponse, error) + Plans(ctx context.Context) (*PlansResponse, error) + RedeemResellerCode(ctx context.Context, req *protos.RedeemResellerCodeRequest) (*protos.BaseResponse, error) + UserCreate(ctx context.Context) (*UserDataResponse, error) + UserData(ctx context.Context) (*UserDataResponse, error) +} + +// NewClient creates a new instance of ProClient +func NewClient(baseURL string, opts *Opts) ProClient { + httpClient := opts.HttpClient + if httpClient == nil { + httpClient = &http.Client{} + } + client := &proClient{ + userConfig: opts.UserConfig, + } + client.webclient = webclient.NewRESTClient(defaultwebclient.SendToURL(httpClient, baseURL, client.setUserHeaders(), nil)) + return client +} + +func (c *proClient) setUserHeaders() func(client *resty.Client, req *resty.Request) error { + return func(client *resty.Client, req *resty.Request) error { + + uc := c.userConfig() + + req.Header.Set("Referer", "http://localhost:37457/") + req.Header.Set("Access-Control-Allow-Headers", strings.Join([]string{ + common.DeviceIdHeader, + common.ProTokenHeader, + common.UserIdHeader, + }, ", ")) + req.Header.Set(common.LocaleHeader, uc.GetLanguage()) + + if req.Header.Get(common.DeviceIdHeader) == "" { + if deviceID := uc.GetDeviceID(); deviceID != "" { + req.Header.Set(common.DeviceIdHeader, deviceID) + } + } + + if req.Header.Get(common.ProTokenHeader) == "" { + if token := uc.GetToken(); token != "" { + req.Header.Set(common.ProTokenHeader, token) + } + } + if req.Header.Get(common.UserIdHeader) == "" { + if userID := uc.GetUserID(); userID != 0 { + req.Header.Set(common.UserIdHeader, strconv.FormatInt(userID, 10)) + } + } + + return nil + } +} + +func (c *proClient) defaultParams() map[string]interface{} { + uc := c.userConfig() + params := map[string]interface{}{ + "locale": uc.GetLanguage(), + } + return params +} + +// EmailExists is used to check if an email address belongs to an existing Pro account +// XXX Deprecated: See https://github.com/getlantern/lantern-internal/issues/4377 +func (c *proClient) EmailExists(ctx context.Context, email string) (*protos.BaseResponse, error) { + var resp protos.BaseResponse + err := c.webclient.GetJSON(ctx, "/email-exists", map[string]interface{}{ + "email": email, + }, &resp) + if err != nil { + return nil, err + } + return &resp, nil +} + +// PaymentRedirect returns a checkout/redirect URL to be used to complete a Lantern Pro purchase with a payment provider +func (c *proClient) PaymentRedirect(ctx context.Context, req *protos.PaymentRedirectRequest) (*PaymentRedirectResponse, error) { + var resp PaymentRedirectResponse + uc := c.userConfig() + req.Locale = uc.GetLanguage() + b, _ := protojson.Marshal(req) + params := make(map[string]interface{}) + json.Unmarshal(b, ¶ms) + err := c.webclient.GetJSON(ctx, "/payment-redirect", params, &resp) + if err != nil { + return nil, err + } + return &resp, nil +} + +// PaymentMethods returns a list of plans along with payment providers and available payment methods +func (c *proClient) PaymentMethods(ctx context.Context) (*PaymentMethodsResponse, error) { + var resp PaymentMethodsResponse + err := c.webclient.GetJSON(ctx, "/plans-v3", c.defaultParams(), &resp) + if err != nil { + return nil, err + } + b, _ := json.Marshal(resp) + log.Debugf("PaymentMethods response is %v", string(b)) + 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.webclient.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) + } + } + } + return &resp, nil +} + +// UserCreate creates a new user +func (c *proClient) UserCreate(ctx context.Context) (*UserDataResponse, error) { + var resp UserDataResponse + err := c.webclient.PostFormReadingJSON(ctx, "/user-create", nil, &resp) + if err != nil { + return nil, errors.New("error fetching user data: %v", err) + } + return &resp, nil +} + +// UserData returns data associated with a user +func (c *proClient) UserData(ctx context.Context) (*UserDataResponse, error) { + var resp UserDataResponse + err := c.webclient.GetJSON(ctx, "/user-data", nil, &resp) + if err != nil { + return nil, errors.New("error fetching user data: %v", err) + } + return &resp, nil +} + +// RedeemResellerCode redeems a reseller code for the given user +func (c *proClient) RedeemResellerCode(ctx context.Context, req *protos.RedeemResellerCodeRequest) (*protos.BaseResponse, error) { + var resp protos.BaseResponse + if err := c.webclient.PostFormReadingJSON(ctx, "/purchase", req, &resp); err != nil { + log.Errorf("Failed to redeem reseller code: %v", err) + return nil, err + } + + return &resp, nil +} + +// DeviceRemove removes the device with the given ID from a user's Pro account +func (c *proClient) DeviceRemove(ctx context.Context, deviceId string) (*LinkResponse, error) { + var resp LinkResponse + params := c.defaultParams() + params["deviceID"] = deviceId + err := c.webclient.PostJSONReadingJSON(ctx, "/user-link-remove", params, nil, &resp) + if err != nil { + return nil, err + } + return &resp, nil +} + +// LinkCodeApprove is used to approve a code to link a device to an existing Pro account +func (c *proClient) LinkCodeApprove(ctx context.Context, code string) (*protos.BaseResponse, error) { + var resp protos.BaseResponse + params := c.defaultParams() + params["code"] = code + err := c.webclient.PostJSONReadingJSON(ctx, "/link-code-approve", params, nil, &resp) + if err != nil { + return nil, err + } + return &resp, nil +} + +// LinkCodeRequest returns a code that can be used to link a device to an existing Pro account +func (c *proClient) LinkCodeRequest(ctx context.Context, deviceName string) (*LinkCodeResponse, error) { + if deviceName == "" { + return nil, errMissingDeviceName + } + var resp LinkCodeResponse + uc := c.userConfig() + err := c.webclient.PostJSONReadingJSON(ctx, "/link-code-request", map[string]interface{}{ + "deviceName": deviceName, + "locale": uc.GetLanguage(), + }, nil, &resp) + if err != nil { + return nil, err + } + b, _ := json.Marshal(resp) + log.Debugf("LinkCodeResponse is %s", string(b)) + return &resp, nil +} diff --git a/internalsdk/pro/pro_test.go b/internalsdk/pro/pro_test.go new file mode 100644 index 000000000..c9eb12244 --- /dev/null +++ b/internalsdk/pro/pro_test.go @@ -0,0 +1,37 @@ +package pro + +import ( + "context" + "net/http" + "testing" + + "github.com/getlantern/golog" + "github.com/getlantern/lantern-client/internalsdk/common" + "github.com/stretchr/testify/assert" +) + +func TestClient(t *testing.T) { + log := golog.LoggerFor("pro-http-test") + client := NewClient("https://api.getiantem.org", &Opts{ + // Just use the default transport since otherwise test setup is difficult. + // This means it does not actually touch the proxying code, but that should + // be tested separately. + HttpClient: &http.Client{}, + UserConfig: func() common.UserConfig { + return common.NewUserConfig( + "Lantern", + "device123", // deviceID + 123, // userID + "token", // token + nil, + "en", // language + ) + }, + }) + res, e := client.Plans(context.Background()) + if !assert.NoError(t, e) { + return + } + log.Debugf("Got response: %v", res) + assert.NotNil(t, res) +} diff --git a/internalsdk/pro/responses.go b/internalsdk/pro/responses.go new file mode 100644 index 000000000..26ca1abd2 --- /dev/null +++ b/internalsdk/pro/responses.go @@ -0,0 +1,37 @@ +package pro + +import ( + "github.com/getlantern/lantern-client/internalsdk/protos" +) + +type PaymentMethodsResponse struct { + *protos.BaseResponse `json:",inline"` + Providers map[string][]protos.PaymentMethod `json:"providers"` +} + +type PaymentRedirectResponse struct { + *protos.BaseResponse `json:",inline"` + Redirect string `json:"redirect"` +} + +type PlansResponse struct { + *protos.BaseResponse `json:",inline"` + Plans []protos.Plan `json:"plans"` +} + +type UserDataResponse struct { + *protos.BaseResponse `json:",inline"` + *protos.User `json:",inline"` +} + +type LinkResponse struct { + *protos.BaseResponse `json:",inline"` + UserID int `json:"userID"` + ProToken string `json:"token"` +} + +type LinkCodeResponse struct { + *protos.BaseResponse `json:",inline"` + Code string + ExpireAt int64 +} diff --git a/internalsdk/pro/webclient/defaultwebclient/rest.go b/internalsdk/pro/webclient/defaultwebclient/rest.go new file mode 100644 index 000000000..91f34e3f0 --- /dev/null +++ b/internalsdk/pro/webclient/defaultwebclient/rest.go @@ -0,0 +1,64 @@ +package defaultwebclient + +import ( + "context" + "fmt" + "net/http" + + "github.com/getlantern/errors" + "github.com/getlantern/golog" + "github.com/getlantern/lantern-client/internalsdk/pro/webclient" + + "github.com/go-resty/resty/v2" +) + +var ( + log = golog.LoggerFor("defaultwebclient") +) + +// Create function that sends requests to the given URL, optionally sending them through a proxy, +// optionally processing requests with the given beforeRequest middleware and/or responses with the given afterResponse middleware. +func SendToURL(httpClient *http.Client, baseURL string, beforeRequest resty.RequestMiddleware, afterResponse resty.ResponseMiddleware) webclient.SendRequest { + c := resty.NewWithClient(httpClient) + if beforeRequest != nil { + c.OnBeforeRequest(beforeRequest) + } + if afterResponse != nil { + c.OnAfterResponse(afterResponse) + } + c.SetBaseURL(baseURL) + + return func(ctx context.Context, method string, path string, reqParams any, body []byte) ([]byte, error) { + req := c.R().SetContext(ctx) + if reqParams != nil { + switch reqParams.(type) { + case map[string]interface{}: + params := reqParams.(map[string]interface{}) + stringParams := make(map[string]string, len(params)) + for key, value := range params { + stringParams[key] = fmt.Sprint(value) + } + if method == http.MethodGet { + req.SetQueryParams(stringParams) + } else { + req.SetFormData(stringParams) + } + default: + req.SetBody(reqParams) + } + } else if body != nil { + req.Body = body + } + + resp, err := req.Execute(method, path) + if err != nil { + return nil, err + } + responseBody := resp.Body() + if resp.StatusCode() < 200 || resp.StatusCode() >= 300 { + log.Errorf("Unexpected status code %d\n\n%v", resp.StatusCode(), string(responseBody)) + return nil, errors.New("Unexpected status code %d", resp.StatusCode()) + } + return responseBody, nil + } +} diff --git a/internalsdk/pro/webclient/webclient.go b/internalsdk/pro/webclient/webclient.go new file mode 100644 index 000000000..d4dab4aeb --- /dev/null +++ b/internalsdk/pro/webclient/webclient.go @@ -0,0 +1,77 @@ +package webclient + +import ( + "context" + "encoding/json" + "net/http" + + "github.com/getlantern/golog" +) + +var ( + log = golog.LoggerFor("webclient") +) + +type RESTClient interface { + // Gets a JSON document from the given path with the given querystring parameters, reading the result into target. + GetJSON(ctx context.Context, path string, params, target any) error + + // Post the given parameters as form data and reads the result JSON into target. + PostFormReadingJSON(ctx context.Context, path string, params, target any) error + + // Post the given body as JSON with the given querystring parameters and reads the result JSON into target. + PostJSONReadingJSON(ctx context.Context, path string, params, body, target any) error +} + +// A function that can send RESTful requests and receive response bodies. +// If specified, params should be encoded as query params for GET requests and as form data for POST and PUT requests. +// If specified, the body bytes should be sent as the body for POST and PUT requests. +// Returns the response body as bytes. +type SendRequest func(ctx context.Context, method string, path string, params any, body []byte) ([]byte, error) + +type restClient struct { + send SendRequest +} + +// Construct a REST client using the given SendRequest function +func NewRESTClient(send SendRequest) RESTClient { + return &restClient{ + send: send, + } +} + +func (c *restClient) GetJSON(ctx context.Context, path string, params, target any) error { + b, err := c.send(ctx, http.MethodGet, path, params, nil) + if err != nil { + return err + } + return unmarshalJSON(path, b, target) +} + +func (c *restClient) PostFormReadingJSON(ctx context.Context, path string, params, target any) error { + b, err := c.send(ctx, http.MethodPost, path, params, nil) + if err != nil { + return err + } + return unmarshalJSON(path, b, target) +} + +func (c *restClient) PostJSONReadingJSON(ctx context.Context, path string, params, body, target any) error { + bodyBytes, err := json.Marshal(body) + if err != nil { + return err + } + b, err := c.send(ctx, http.MethodPost, path, params, bodyBytes) + if err != nil { + return err + } + return unmarshalJSON(path, b, target) +} + +func unmarshalJSON(path string, b []byte, target any) error { + err := json.Unmarshal(b, target) + if err != nil { + log.Errorf("Error unmarshalling JSON from %v: %v\n\n", path, err, string(b)) + } + return err +} diff --git a/internalsdk/protos/vpn.pb.go b/internalsdk/protos/vpn.pb.go index 4d9e0a68f..c9cc0aefc 100644 --- a/internalsdk/protos/vpn.pb.go +++ b/internalsdk/protos/vpn.pb.go @@ -345,11 +345,12 @@ type Plan struct { BestValue bool `protobuf:"varint,3,opt,name=bestValue,proto3" json:"bestValue,omitempty"` UsdPrice int64 `protobuf:"varint,4,opt,name=usdPrice,proto3" json:"usdPrice,omitempty"` Price map[string]int64 `protobuf:"bytes,5,rep,name=price,proto3" json:"price,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"` - TotalCostBilledOneTime string `protobuf:"bytes,6,opt,name=totalCostBilledOneTime,proto3" json:"totalCostBilledOneTime,omitempty"` - OneMonthCost string `protobuf:"bytes,7,opt,name=oneMonthCost,proto3" json:"oneMonthCost,omitempty"` - TotalCost string `protobuf:"bytes,8,opt,name=totalCost,proto3" json:"totalCost,omitempty"` - FormattedBonus string `protobuf:"bytes,9,opt,name=formattedBonus,proto3" json:"formattedBonus,omitempty"` - RenewalText string `protobuf:"bytes,10,opt,name=renewalText,proto3" json:"renewalText,omitempty"` + ExpectedMonthlyPrice map[string]int64 `protobuf:"bytes,6,rep,name=expectedMonthlyPrice,proto3" json:"expectedMonthlyPrice,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"` + TotalCostBilledOneTime string `protobuf:"bytes,7,opt,name=totalCostBilledOneTime,proto3" json:"totalCostBilledOneTime,omitempty"` + OneMonthCost string `protobuf:"bytes,8,opt,name=oneMonthCost,proto3" json:"oneMonthCost,omitempty"` + TotalCost string `protobuf:"bytes,9,opt,name=totalCost,proto3" json:"totalCost,omitempty"` + FormattedBonus string `protobuf:"bytes,10,opt,name=formattedBonus,proto3" json:"formattedBonus,omitempty"` + RenewalText string `protobuf:"bytes,11,opt,name=renewalText,proto3" json:"renewalText,omitempty"` } func (x *Plan) Reset() { @@ -419,6 +420,13 @@ func (x *Plan) GetPrice() map[string]int64 { return nil } +func (x *Plan) GetExpectedMonthlyPrice() map[string]int64 { + if x != nil { + return x.ExpectedMonthlyPrice + } + return nil +} + func (x *Plan) GetTotalCostBilledOneTime() string { if x != nil { return x.TotalCostBilledOneTime @@ -459,8 +467,9 @@ type PaymentProviders struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - LogoUrls []string `protobuf:"bytes,2,rep,name=logoUrls,proto3" json:"logoUrls,omitempty"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Data map[string]string `protobuf:"bytes,2,rep,name=data,proto3" json:"data,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + LogoUrls []string `protobuf:"bytes,3,rep,name=logoUrls,proto3" json:"logoUrls,omitempty"` } func (x *PaymentProviders) Reset() { @@ -502,6 +511,13 @@ func (x *PaymentProviders) GetName() string { return "" } +func (x *PaymentProviders) GetData() map[string]string { + if x != nil { + return x.Data + } + return nil +} + func (x *PaymentProviders) GetLogoUrls() []string { if x != nil { return x.LogoUrls @@ -573,14 +589,15 @@ type User struct { Email string `protobuf:"bytes,2,opt,name=email,proto3" json:"email,omitempty"` Telephone string `protobuf:"bytes,3,opt,name=telephone,proto3" json:"telephone,omitempty"` UserStatus string `protobuf:"bytes,4,opt,name=userStatus,proto3" json:"userStatus,omitempty"` - Locale string `protobuf:"bytes,5,opt,name=locale,proto3" json:"locale,omitempty"` - Expiration int64 `protobuf:"varint,6,opt,name=expiration,proto3" json:"expiration,omitempty"` - Devices []*Device `protobuf:"bytes,7,rep,name=devices,proto3" json:"devices,omitempty"` - Code string `protobuf:"bytes,8,opt,name=code,proto3" json:"code,omitempty"` - ExpireAt int64 `protobuf:"varint,9,opt,name=expireAt,proto3" json:"expireAt,omitempty"` - Referral string `protobuf:"bytes,10,opt,name=referral,proto3" json:"referral,omitempty"` - Token string `protobuf:"bytes,11,opt,name=token,proto3" json:"token,omitempty"` - YinbiEnabled bool `protobuf:"varint,12,opt,name=yinbiEnabled,proto3" json:"yinbiEnabled,omitempty"` + UserLevel string `protobuf:"bytes,5,opt,name=userLevel,proto3" json:"userLevel,omitempty"` + Locale string `protobuf:"bytes,6,opt,name=locale,proto3" json:"locale,omitempty"` + Expiration int64 `protobuf:"varint,7,opt,name=expiration,proto3" json:"expiration,omitempty"` + Devices []*Device `protobuf:"bytes,8,rep,name=devices,proto3" json:"devices,omitempty"` + Code string `protobuf:"bytes,9,opt,name=code,proto3" json:"code,omitempty"` + ExpireAt int64 `protobuf:"varint,10,opt,name=expireAt,proto3" json:"expireAt,omitempty"` + Referral string `protobuf:"bytes,11,opt,name=referral,proto3" json:"referral,omitempty"` + Token string `protobuf:"bytes,12,opt,name=token,proto3" json:"token,omitempty"` + YinbiEnabled bool `protobuf:"varint,13,opt,name=yinbiEnabled,proto3" json:"yinbiEnabled,omitempty"` } func (x *User) Reset() { @@ -643,6 +660,13 @@ func (x *User) GetUserStatus() string { return "" } +func (x *User) GetUserLevel() string { + if x != nil { + return x.UserLevel + } + return "" +} + func (x *User) GetLocale() string { if x != nil { return x.Locale @@ -700,7 +724,7 @@ func (x *User) GetYinbiEnabled() bool { } // API -type APIResponse struct { +type BaseResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields @@ -710,8 +734,8 @@ type APIResponse struct { ErrorId string `protobuf:"bytes,3,opt,name=errorId,proto3" json:"errorId,omitempty"` } -func (x *APIResponse) Reset() { - *x = APIResponse{} +func (x *BaseResponse) Reset() { + *x = BaseResponse{} if protoimpl.UnsafeEnabled { mi := &file_protos_shared_vpn_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -719,13 +743,13 @@ func (x *APIResponse) Reset() { } } -func (x *APIResponse) String() string { +func (x *BaseResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*APIResponse) ProtoMessage() {} +func (*BaseResponse) ProtoMessage() {} -func (x *APIResponse) ProtoReflect() protoreflect.Message { +func (x *BaseResponse) ProtoReflect() protoreflect.Message { mi := &file_protos_shared_vpn_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -737,32 +761,285 @@ func (x *APIResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use APIResponse.ProtoReflect.Descriptor instead. -func (*APIResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use BaseResponse.ProtoReflect.Descriptor instead. +func (*BaseResponse) Descriptor() ([]byte, []int) { return file_protos_shared_vpn_proto_rawDescGZIP(), []int{9} } -func (x *APIResponse) GetStatus() string { +func (x *BaseResponse) GetStatus() string { if x != nil { return x.Status } return "" } -func (x *APIResponse) GetError() string { +func (x *BaseResponse) GetError() string { if x != nil { return x.Error } return "" } -func (x *APIResponse) GetErrorId() string { +func (x *BaseResponse) GetErrorId() string { if x != nil { return x.ErrorId } return "" } +type PaymentRedirectRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Plan string `protobuf:"bytes,1,opt,name=plan,proto3" json:"plan,omitempty"` + Provider string `protobuf:"bytes,2,opt,name=provider,proto3" json:"provider,omitempty"` + Currency string `protobuf:"bytes,3,opt,name=currency,proto3" json:"currency,omitempty"` + Email string `protobuf:"bytes,4,opt,name=email,proto3" json:"email,omitempty"` + DeviceName string `protobuf:"bytes,5,opt,name=deviceName,proto3" json:"deviceName,omitempty"` + CountryCode string `protobuf:"bytes,6,opt,name=countryCode,proto3" json:"countryCode,omitempty"` + Locale string `protobuf:"bytes,7,opt,name=locale,proto3" json:"locale,omitempty"` +} + +func (x *PaymentRedirectRequest) Reset() { + *x = PaymentRedirectRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_shared_vpn_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PaymentRedirectRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PaymentRedirectRequest) ProtoMessage() {} + +func (x *PaymentRedirectRequest) ProtoReflect() protoreflect.Message { + mi := &file_protos_shared_vpn_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PaymentRedirectRequest.ProtoReflect.Descriptor instead. +func (*PaymentRedirectRequest) Descriptor() ([]byte, []int) { + return file_protos_shared_vpn_proto_rawDescGZIP(), []int{10} +} + +func (x *PaymentRedirectRequest) GetPlan() string { + if x != nil { + return x.Plan + } + return "" +} + +func (x *PaymentRedirectRequest) GetProvider() string { + if x != nil { + return x.Provider + } + return "" +} + +func (x *PaymentRedirectRequest) GetCurrency() string { + if x != nil { + return x.Currency + } + return "" +} + +func (x *PaymentRedirectRequest) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *PaymentRedirectRequest) GetDeviceName() string { + if x != nil { + return x.DeviceName + } + return "" +} + +func (x *PaymentRedirectRequest) GetCountryCode() string { + if x != nil { + return x.CountryCode + } + return "" +} + +func (x *PaymentRedirectRequest) GetLocale() string { + if x != nil { + return x.Locale + } + return "" +} + +type RedeemResellerCodeRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` + ResellerCode string `protobuf:"bytes,2,opt,name=resellerCode,proto3" json:"resellerCode,omitempty"` + DeviceName string `protobuf:"bytes,3,opt,name=deviceName,proto3" json:"deviceName,omitempty"` + Currency string `protobuf:"bytes,4,opt,name=currency,proto3" json:"currency,omitempty"` + IdempotencyKey string `protobuf:"bytes,5,opt,name=idempotencyKey,proto3" json:"idempotencyKey,omitempty"` + Provider string `protobuf:"bytes,6,opt,name=provider,proto3" json:"provider,omitempty"` +} + +func (x *RedeemResellerCodeRequest) Reset() { + *x = RedeemResellerCodeRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_shared_vpn_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RedeemResellerCodeRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RedeemResellerCodeRequest) ProtoMessage() {} + +func (x *RedeemResellerCodeRequest) ProtoReflect() protoreflect.Message { + mi := &file_protos_shared_vpn_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RedeemResellerCodeRequest.ProtoReflect.Descriptor instead. +func (*RedeemResellerCodeRequest) Descriptor() ([]byte, []int) { + return file_protos_shared_vpn_proto_rawDescGZIP(), []int{11} +} + +func (x *RedeemResellerCodeRequest) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *RedeemResellerCodeRequest) GetResellerCode() string { + if x != nil { + return x.ResellerCode + } + return "" +} + +func (x *RedeemResellerCodeRequest) GetDeviceName() string { + if x != nil { + return x.DeviceName + } + return "" +} + +func (x *RedeemResellerCodeRequest) GetCurrency() string { + if x != nil { + return x.Currency + } + return "" +} + +func (x *RedeemResellerCodeRequest) GetIdempotencyKey() string { + if x != nil { + return x.IdempotencyKey + } + return "" +} + +func (x *RedeemResellerCodeRequest) GetProvider() string { + if x != nil { + return x.Provider + } + return "" +} + +type PaymentRedirectResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` + Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` + ErrorId string `protobuf:"bytes,3,opt,name=errorId,proto3" json:"errorId,omitempty"` + Redirect string `protobuf:"bytes,4,opt,name=redirect,proto3" json:"redirect,omitempty"` +} + +func (x *PaymentRedirectResponse) Reset() { + *x = PaymentRedirectResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_shared_vpn_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PaymentRedirectResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PaymentRedirectResponse) ProtoMessage() {} + +func (x *PaymentRedirectResponse) ProtoReflect() protoreflect.Message { + mi := &file_protos_shared_vpn_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PaymentRedirectResponse.ProtoReflect.Descriptor instead. +func (*PaymentRedirectResponse) Descriptor() ([]byte, []int) { + return file_protos_shared_vpn_proto_rawDescGZIP(), []int{12} +} + +func (x *PaymentRedirectResponse) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *PaymentRedirectResponse) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +func (x *PaymentRedirectResponse) GetErrorId() string { + if x != nil { + return x.ErrorId + } + return "" +} + +func (x *PaymentRedirectResponse) GetRedirect() string { + if x != nil { + return x.Redirect + } + return "" +} + type LinkResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -778,7 +1055,7 @@ type LinkResponse struct { func (x *LinkResponse) Reset() { *x = LinkResponse{} if protoimpl.UnsafeEnabled { - mi := &file_protos_shared_vpn_proto_msgTypes[10] + mi := &file_protos_shared_vpn_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -791,7 +1068,7 @@ func (x *LinkResponse) String() string { func (*LinkResponse) ProtoMessage() {} func (x *LinkResponse) ProtoReflect() protoreflect.Message { - mi := &file_protos_shared_vpn_proto_msgTypes[10] + mi := &file_protos_shared_vpn_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -804,7 +1081,7 @@ func (x *LinkResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use LinkResponse.ProtoReflect.Descriptor instead. func (*LinkResponse) Descriptor() ([]byte, []int) { - return file_protos_shared_vpn_proto_rawDescGZIP(), []int{10} + return file_protos_shared_vpn_proto_rawDescGZIP(), []int{13} } func (x *LinkResponse) GetUserID() int64 { @@ -875,7 +1152,7 @@ var file_protos_shared_vpn_proto_rawDesc = []byte{ 0x52, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x22, 0x2c, 0x0a, 0x07, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x21, 0x0a, 0x07, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x07, - 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x22, 0x98, 0x03, 0x0a, 0x04, 0x50, 0x6c, 0x61, 0x6e, + 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x22, 0xb6, 0x04, 0x0a, 0x04, 0x50, 0x6c, 0x61, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, @@ -885,70 +1162,123 @@ var file_protos_shared_vpn_proto_rawDesc = []byte{ 0x28, 0x03, 0x52, 0x08, 0x75, 0x73, 0x64, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x26, 0x0a, 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x2e, 0x50, 0x72, 0x69, 0x63, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x05, 0x70, - 0x72, 0x69, 0x63, 0x65, 0x12, 0x36, 0x0a, 0x16, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x73, - 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x65, 0x64, 0x4f, 0x6e, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x73, 0x74, 0x42, - 0x69, 0x6c, 0x6c, 0x65, 0x64, 0x4f, 0x6e, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x0c, - 0x6f, 0x6e, 0x65, 0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x43, 0x6f, 0x73, 0x74, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0c, 0x6f, 0x6e, 0x65, 0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x43, 0x6f, 0x73, 0x74, - 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x73, 0x74, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x73, 0x74, 0x12, 0x26, - 0x0a, 0x0e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x74, 0x65, 0x64, 0x42, 0x6f, 0x6e, 0x75, 0x73, - 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x74, 0x65, - 0x64, 0x42, 0x6f, 0x6e, 0x75, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x72, 0x65, 0x6e, 0x65, 0x77, 0x61, - 0x6c, 0x54, 0x65, 0x78, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x6e, - 0x65, 0x77, 0x61, 0x6c, 0x54, 0x65, 0x78, 0x74, 0x1a, 0x38, 0x0a, 0x0a, 0x50, 0x72, 0x69, 0x63, - 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, - 0x38, 0x01, 0x22, 0x42, 0x0a, 0x10, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x6f, - 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x6f, - 0x67, 0x6f, 0x55, 0x72, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x6f, - 0x67, 0x6f, 0x55, 0x72, 0x6c, 0x73, 0x22, 0x58, 0x0a, 0x0d, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, - 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, - 0x2f, 0x0a, 0x09, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x6f, 0x76, - 0x69, 0x64, 0x65, 0x72, 0x73, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, - 0x22, 0xd3, 0x02, 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, - 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x65, 0x6c, 0x65, 0x70, - 0x68, 0x6f, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x65, 0x6c, 0x65, - 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x65, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x65, 0x12, 0x1e, 0x0a, - 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, - 0x07, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x07, - 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x07, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, - 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x63, 0x6f, 0x64, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x41, 0x74, - 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x41, 0x74, - 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x61, 0x6c, 0x18, 0x0a, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x61, 0x6c, 0x12, 0x14, 0x0a, 0x05, - 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, - 0x65, 0x6e, 0x12, 0x22, 0x0a, 0x0c, 0x79, 0x69, 0x6e, 0x62, 0x69, 0x45, 0x6e, 0x61, 0x62, 0x6c, - 0x65, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x79, 0x69, 0x6e, 0x62, 0x69, 0x45, - 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x55, 0x0a, 0x0b, 0x41, 0x50, 0x49, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x14, 0x0a, - 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, - 0x72, 0x6f, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x49, 0x64, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x49, 0x64, 0x22, 0x84, 0x01, - 0x0a, 0x0c, 0x4c, 0x69, 0x6e, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, - 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, - 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x16, 0x0a, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x72, - 0x72, 0x6f, 0x72, 0x49, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x72, 0x72, - 0x6f, 0x72, 0x49, 0x64, 0x42, 0x1b, 0x0a, 0x10, 0x69, 0x6f, 0x2e, 0x6c, 0x61, 0x6e, 0x74, 0x65, - 0x72, 0x6e, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x5a, 0x07, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x72, 0x69, 0x63, 0x65, 0x12, 0x53, 0x0a, 0x14, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, + 0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x6c, 0x79, 0x50, 0x72, 0x69, 0x63, 0x65, 0x18, 0x06, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x2e, 0x45, 0x78, 0x70, 0x65, 0x63, 0x74, + 0x65, 0x64, 0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x6c, 0x79, 0x50, 0x72, 0x69, 0x63, 0x65, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x52, 0x14, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4d, 0x6f, 0x6e, + 0x74, 0x68, 0x6c, 0x79, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x36, 0x0a, 0x16, 0x74, 0x6f, 0x74, + 0x61, 0x6c, 0x43, 0x6f, 0x73, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x65, 0x64, 0x4f, 0x6e, 0x65, 0x54, + 0x69, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x74, 0x6f, 0x74, 0x61, 0x6c, + 0x43, 0x6f, 0x73, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x65, 0x64, 0x4f, 0x6e, 0x65, 0x54, 0x69, 0x6d, + 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x6f, 0x6e, 0x65, 0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x43, 0x6f, 0x73, + 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, 0x6e, 0x65, 0x4d, 0x6f, 0x6e, 0x74, + 0x68, 0x43, 0x6f, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, + 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, + 0x6f, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x74, 0x65, 0x64, + 0x42, 0x6f, 0x6e, 0x75, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x66, 0x6f, 0x72, + 0x6d, 0x61, 0x74, 0x74, 0x65, 0x64, 0x42, 0x6f, 0x6e, 0x75, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x72, + 0x65, 0x6e, 0x65, 0x77, 0x61, 0x6c, 0x54, 0x65, 0x78, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x72, 0x65, 0x6e, 0x65, 0x77, 0x61, 0x6c, 0x54, 0x65, 0x78, 0x74, 0x1a, 0x38, 0x0a, + 0x0a, 0x50, 0x72, 0x69, 0x63, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, + 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x47, 0x0a, 0x19, 0x45, 0x78, 0x70, 0x65, 0x63, + 0x74, 0x65, 0x64, 0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x6c, 0x79, 0x50, 0x72, 0x69, 0x63, 0x65, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, + 0x22, 0xac, 0x01, 0x0a, 0x10, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x6f, 0x76, + 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x64, 0x61, 0x74, + 0x61, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x6f, + 0x67, 0x6f, 0x55, 0x72, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x6f, + 0x67, 0x6f, 0x55, 0x72, 0x6c, 0x73, 0x1a, 0x37, 0x0a, 0x09, 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, + 0x58, 0x0a, 0x0d, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, + 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x2f, 0x0a, 0x09, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x50, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x52, 0x09, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x22, 0xf1, 0x02, 0x0a, 0x04, 0x55, 0x73, + 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, + 0x61, 0x69, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, + 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x12, 0x1e, + 0x0a, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1c, + 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x75, 0x73, 0x65, 0x72, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x16, 0x0a, 0x06, + 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6c, 0x6f, + 0x63, 0x61, 0x6c, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x07, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, + 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x07, + 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, + 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x65, + 0x78, 0x70, 0x69, 0x72, 0x65, 0x41, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x65, + 0x78, 0x70, 0x69, 0x72, 0x65, 0x41, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x66, 0x65, 0x72, + 0x72, 0x61, 0x6c, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x66, 0x65, 0x72, + 0x72, 0x61, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x0c, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x22, 0x0a, 0x0c, 0x79, 0x69, 0x6e, + 0x62, 0x69, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x0c, 0x79, 0x69, 0x6e, 0x62, 0x69, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x56, 0x0a, + 0x0c, 0x42, 0x61, 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x65, + 0x72, 0x72, 0x6f, 0x72, 0x49, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x72, + 0x72, 0x6f, 0x72, 0x49, 0x64, 0x22, 0xd4, 0x01, 0x0a, 0x16, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, + 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x14, 0x0a, 0x05, + 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, + 0x69, 0x6c, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x61, + 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, + 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, + 0x43, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x65, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x65, 0x22, 0xd5, 0x01, 0x0a, + 0x19, 0x52, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x52, 0x65, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x43, + 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, + 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, + 0x12, 0x22, 0x0a, 0x0c, 0x72, 0x65, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, + 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x61, + 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, + 0x12, 0x26, 0x0a, 0x0e, 0x69, 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x4b, + 0x65, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x69, 0x64, 0x65, 0x6d, 0x70, 0x6f, + 0x74, 0x65, 0x6e, 0x63, 0x79, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x64, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x64, 0x65, 0x72, 0x22, 0x7d, 0x0a, 0x17, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, + 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x18, 0x0a, + 0x07, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x49, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x64, 0x69, 0x72, + 0x65, 0x63, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x64, 0x69, 0x72, + 0x65, 0x63, 0x74, 0x22, 0x84, 0x01, 0x0a, 0x0c, 0x4c, 0x69, 0x6e, 0x6b, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x12, 0x14, 0x0a, 0x05, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, + 0x65, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, + 0x72, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, + 0x12, 0x18, 0x0a, 0x07, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x49, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x49, 0x64, 0x42, 0x1b, 0x0a, 0x10, 0x69, 0x6f, + 0x2e, 0x6c, 0x61, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x5a, 0x07, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -963,31 +1293,38 @@ func file_protos_shared_vpn_proto_rawDescGZIP() []byte { return file_protos_shared_vpn_proto_rawDescData } -var file_protos_shared_vpn_proto_msgTypes = make([]protoimpl.MessageInfo, 12) +var file_protos_shared_vpn_proto_msgTypes = make([]protoimpl.MessageInfo, 17) var file_protos_shared_vpn_proto_goTypes = []interface{}{ - (*ServerInfo)(nil), // 0: ServerInfo - (*Bandwidth)(nil), // 1: Bandwidth - (*AppData)(nil), // 2: AppData - (*Device)(nil), // 3: Device - (*Devices)(nil), // 4: Devices - (*Plan)(nil), // 5: Plan - (*PaymentProviders)(nil), // 6: PaymentProviders - (*PaymentMethod)(nil), // 7: PaymentMethod - (*User)(nil), // 8: User - (*APIResponse)(nil), // 9: APIResponse - (*LinkResponse)(nil), // 10: LinkResponse - nil, // 11: Plan.PriceEntry + (*ServerInfo)(nil), // 0: ServerInfo + (*Bandwidth)(nil), // 1: Bandwidth + (*AppData)(nil), // 2: AppData + (*Device)(nil), // 3: Device + (*Devices)(nil), // 4: Devices + (*Plan)(nil), // 5: Plan + (*PaymentProviders)(nil), // 6: PaymentProviders + (*PaymentMethod)(nil), // 7: PaymentMethod + (*User)(nil), // 8: User + (*BaseResponse)(nil), // 9: BaseResponse + (*PaymentRedirectRequest)(nil), // 10: PaymentRedirectRequest + (*RedeemResellerCodeRequest)(nil), // 11: RedeemResellerCodeRequest + (*PaymentRedirectResponse)(nil), // 12: PaymentRedirectResponse + (*LinkResponse)(nil), // 13: LinkResponse + nil, // 14: Plan.PriceEntry + nil, // 15: Plan.ExpectedMonthlyPriceEntry + nil, // 16: PaymentProviders.DataEntry } var file_protos_shared_vpn_proto_depIdxs = []int32{ 3, // 0: Devices.devices:type_name -> Device - 11, // 1: Plan.price:type_name -> Plan.PriceEntry - 6, // 2: PaymentMethod.providers:type_name -> PaymentProviders - 3, // 3: User.devices:type_name -> Device - 4, // [4:4] is the sub-list for method output_type - 4, // [4:4] is the sub-list for method input_type - 4, // [4:4] is the sub-list for extension type_name - 4, // [4:4] is the sub-list for extension extendee - 0, // [0:4] is the sub-list for field type_name + 14, // 1: Plan.price:type_name -> Plan.PriceEntry + 15, // 2: Plan.expectedMonthlyPrice:type_name -> Plan.ExpectedMonthlyPriceEntry + 16, // 3: PaymentProviders.data:type_name -> PaymentProviders.DataEntry + 6, // 4: PaymentMethod.providers:type_name -> PaymentProviders + 3, // 5: User.devices:type_name -> Device + 6, // [6:6] is the sub-list for method output_type + 6, // [6:6] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name } func init() { file_protos_shared_vpn_proto_init() } @@ -1105,7 +1442,7 @@ func file_protos_shared_vpn_proto_init() { } } file_protos_shared_vpn_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*APIResponse); i { + switch v := v.(*BaseResponse); i { case 0: return &v.state case 1: @@ -1117,6 +1454,42 @@ func file_protos_shared_vpn_proto_init() { } } file_protos_shared_vpn_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PaymentRedirectRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_shared_vpn_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RedeemResellerCodeRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_shared_vpn_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PaymentRedirectResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_shared_vpn_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*LinkResponse); i { case 0: return &v.state @@ -1135,7 +1508,7 @@ func file_protos_shared_vpn_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_protos_shared_vpn_proto_rawDesc, NumEnums: 0, - NumMessages: 12, + NumMessages: 17, NumExtensions: 0, NumServices: 0, }, diff --git a/internalsdk/session_model.go b/internalsdk/session_model.go index ba3d55ef4..5467416e2 100644 --- a/internalsdk/session_model.go +++ b/internalsdk/session_model.go @@ -1,14 +1,18 @@ package internalsdk import ( + "context" "fmt" + "net/http" "path/filepath" "strconv" + "time" "github.com/getlantern/errors" - "github.com/getlantern/flashlight/v7/common" "github.com/getlantern/flashlight/v7/logging" - "github.com/getlantern/lantern-client/internalsdk/apimodels" + "github.com/getlantern/flashlight/v7/proxied" + "github.com/getlantern/lantern-client/internalsdk/common" + "github.com/getlantern/lantern-client/internalsdk/pro" "github.com/getlantern/lantern-client/internalsdk/protos" "github.com/getlantern/pathdb" "github.com/getlantern/pathdb/minisql" @@ -18,6 +22,7 @@ import ( // SessionModel is a custom model derived from the baseModel. type SessionModel struct { *baseModel + proClient pro.ProClient } // List of we are using for Session Model @@ -87,8 +92,30 @@ func NewSessionModel(mdb minisql.DB, opts *SessionModelOpts) (*SessionModel, err base.db.RegisterType(2000, &protos.Devices{}) } m := &SessionModel{baseModel: base} + + m.proClient = pro.NewClient(fmt.Sprintf("https://%s", common.ProAPIHost), &pro.Opts{ + HttpClient: &http.Client{ + Transport: proxied.ParallelForIdempotent(), + Timeout: 30 * time.Second, + }, + UserConfig: func() common.UserConfig { + deviceID, _ := m.GetDeviceID() + userID, _ := m.GetUserID() + token, _ := m.GetToken() + lang, _ := m.Locale() + return common.NewUserConfig( + common.DefaultAppName, + deviceID, + userID, + token, + nil, + lang, + ) + }, + }) + m.baseModel.doInvokeMethod = m.doInvokeMethod - return m, m.initSessionModel(opts) + return m, m.initSessionModel(context.Background(), opts) } func (m *SessionModel) doInvokeMethod(method string, arguments Arguments) (interface{}, error) { @@ -162,7 +189,7 @@ func (m *SessionModel) doInvokeMethod(method string, arguments Arguments) (inter } return true, nil case "createUser": - err := userCreate(m.baseModel, arguments.Scalar().String()) + err := m.userCreate(context.Background(), arguments.Scalar().String()) if err != nil { return nil, err } @@ -194,7 +221,7 @@ func (m *SessionModel) StartService(configDir string, } // InvokeMethod handles method invocations on the SessionModel. -func (m *SessionModel) initSessionModel(opts *SessionModelOpts) error { +func (m *SessionModel) initSessionModel(ctx context.Context, opts *SessionModelOpts) error { // Check if email if empty email, err := pathdb.Get[string](m.db, pathEmailAddress) if err != nil { @@ -258,14 +285,14 @@ func (m *SessionModel) initSessionModel(opts *SessionModelOpts) error { return err } // Create user - err = userCreate(m.baseModel, local) + err = m.userCreate(ctx, local) if err != nil { return err } } // Get all user details - err = userDetail(m) + err = m.userDetail(ctx) if err != nil { return err } @@ -400,19 +427,9 @@ func setLanguage(m *baseModel, lang string) error { }) } -func setDevices(m *baseModel, devices []apimodels.UserDevice) error { - var protoDevices []*protos.Device - for _, device := range devices { - protoDevice := &protos.Device{ - Id: device.ID, - Name: device.Name, - Created: device.Created, - } - protoDevices = append(protoDevices, protoDevice) - } - +func setDevices(m *baseModel, devices []*protos.Device) error { pathdb.Mutate(m.db, func(tx pathdb.TX) error { - pathdb.Put(tx, pathDevices, protoDevices, "") + pathdb.Put(tx, pathDevices, devices, "") return nil }) return nil @@ -577,80 +594,61 @@ func setUserIdAndToken(m *baseModel, userId int, token string) error { }) } -// Create user -// Todo-: Create Sprate http client to manag and reuse client -func userCreate(m *baseModel, local string) error { - deviceID, err := pathdb.Get[string](m.db, pathDeviceID) - if err != nil { - return err - } - - userResponse, err := apimodels.UserCreate(deviceID, local) +// userCreate creates a new user and stores it in pathdb +func (session *SessionModel) userCreate(ctx context.Context, local string) error { + resp, err := session.proClient.UserCreate(ctx) if err != nil { log.Errorf("Error sending request: %v", err) return err } - + user := resp.User //Save user id and token - err = setUserIdAndToken(m, int(userResponse.UserID), userResponse.Token) + err = setUserIdAndToken(session.baseModel, int(user.UserId), user.Token) if err != nil { return err } - log.Debugf("Created new Lantern user: %+v", userResponse) + log.Debugf("Created new Lantern user: %+v", user) return nil } -func userDetail(session *SessionModel) error { - deviecId, err := session.GetDeviceID() - if err != nil { - return err - } - userId, err := session.GetUserID() - if err != nil { - return err - } - token, err := session.GetToken() - if err != nil { - return err - } - userIdStr := fmt.Sprintf("%d", userId) - userDetail, err := apimodels.FechUserDetail(deviecId, userIdStr, token) +func (session *SessionModel) userDetail(ctx context.Context) error { + resp, err := session.proClient.UserData(ctx) if err != nil { return nil } - log.Debugf("User detail: %+v", userDetail) - err = cacheUserDetail(session.baseModel, userDetail) + log.Debugf("User detail: %+v", resp.User) + err = cacheUserDetail(session.baseModel, resp.User) if err != nil { return err } return nil } -func cacheUserDetail(m *baseModel, userDetail *apimodels.UserDetailResponse) error { +func cacheUserDetail(m *baseModel, user *protos.User) error { //Save user refferal code - if userDetail.Referral != "" { - err := setReferalCode(m, userDetail.Referral) + if user.Referral != "" { + err := setReferalCode(m, user.Referral) if err != nil { return err } } - if userDetail.UserStatus != "" && userDetail.UserStatus == "active" && userDetail.UserLevel == "pro" { + if user.UserStatus != "" && user.UserStatus == "active" && user.UserLevel == "pro" { setProUser(m, true) } else { setProUser(m, false) } - err := setUserLevel(m, userDetail.UserLevel) + err := setUserLevel(m, user.UserLevel) if err != nil { return err } //Store all device - err = setDevices(m, userDetail.Devices) + err = setDevices(m, user.Devices) if err != nil { return err } - log.Debugf("User caching successful: %+v", userDetail) - return setUserIdAndToken(m, int(userDetail.UserID), userDetail.Token) + log.Debugf("User caching successful: %+v", user) + return setUserIdAndToken(m, int(user.UserId), user.Token) } func reportIssue(session *SessionModel, email string, issue string, description string) error { diff --git a/lib/account/account.dart b/lib/account/account.dart index 718f43d81..34f23a9c7 100644 --- a/lib/account/account.dart +++ b/lib/account/account.dart @@ -62,15 +62,14 @@ class AccountMenu extends StatelessWidget { ) : const SizedBox(), ), - if (Platform.isAndroid) - ListItemFactory.settingsItem( - key: AppKeys.upgrade_lantern_pro, - icon: ImagePaths.pro_icon_black, - content: 'Upgrade to Lantern Pro'.i18n, - onTap: () { - upgradeToLanternPro(context); - }, - ), + ListItemFactory.settingsItem( + key: AppKeys.upgrade_lantern_pro, + icon: ImagePaths.pro_icon_black, + content: 'Upgrade to Lantern Pro'.i18n, + onTap: () { + upgradeToLanternPro(context); + }, + ), ListItemFactory.settingsItem( icon: ImagePaths.star, content: 'Invite Friends'.i18n, diff --git a/lib/common/ffi_list_subscriber.dart b/lib/common/ffi_list_subscriber.dart index 7ef97a8e8..0de3db544 100644 --- a/lib/common/ffi_list_subscriber.dart +++ b/lib/common/ffi_list_subscriber.dart @@ -21,7 +21,7 @@ class FfiListNotifier extends SubscribedNotifier> { var result = jsonDecode(ffiFunction().toDartString()); if (result is List) { for (var item in result) { - var id = item['id']; + var id = item['id'] ?? item['name']; value.map[id] = fromJsonModel(item) as T; } } else if (result is Map) { diff --git a/lib/common/session_model.dart b/lib/common/session_model.dart index c4f24f036..82276d295 100644 --- a/lib/common/session_model.dart +++ b/lib/common/session_model.dart @@ -479,13 +479,21 @@ class SessionModel extends Model { } Plan planFromJson(Map item) { - final locale = Localization.locale; - final formatCurrency = NumberFormat.simpleCurrency(locale: locale); - final currency = formatCurrency.currencyName != null - ? formatCurrency.currencyName!.toLowerCase() - : "usd"; + final formatCurrency = NumberFormat.simpleCurrency(); + var currency = formatCurrency.currencyName != null ? formatCurrency.currencyName!.toLowerCase() : "usd"; final res = jsonEncode(item); final plan = Plan.create()..mergeFromProto3Json(jsonDecode(res)); + if (plan.price[currency] == null) { + final splitted = plan.id.split('-'); + if (splitted.length == 3) { + currency = splitted[1]; + } + } + + if (plan.price[currency] == null) { + return plan; + } + final price = plan.price[currency] as Int64; if (plan.price[currency] != null) { final price = plan.price[currency] as Int64; plan.totalCost = formatCurrency.format(price.toInt() / 100).toString(); @@ -494,6 +502,11 @@ class SessionModel extends Model { ' ' + 'billed_one_time'.i18n; } + plan.totalCost = formatCurrency.format(price.toInt() / 100).toString(); + plan.totalCostBilledOneTime = + formatCurrency.format(price.toInt() / 100).toString() + + ' ' + + 'billed_one_time'.i18n; return plan; } diff --git a/lib/ffi.dart b/lib/ffi.dart index b5c657372..3e73ee8a7 100644 --- a/lib/ffi.dart +++ b/lib/ffi.dart @@ -1,5 +1,4 @@ import 'dart:ffi'; // For FFI -import 'dart:io'; import 'package:ffi/src/utf8.dart'; import 'package:lantern/common/common.dart'; @@ -48,6 +47,10 @@ Future ffiUserData() async { // checkAPIError throws a PlatformException if the API response contains an error void checkAPIError(result, errorMessage) { + if(result is String){ + final errorMessageMap = jsonDecode(result); + throw PlatformException(code:errorMessageMap.toString(), message: errorMessage); + } if (result.error != "") { throw PlatformException(code: result.error, message: errorMessage); } @@ -58,7 +61,7 @@ Future ffiApproveDevice(String code) async { .approveDevice(code.toPointerChar()) .cast() .toDartString(); - final result = APIResponse.create()..mergeFromProto3Json(jsonDecode(json)); + final result = BaseResponse.create()..mergeFromProto3Json(jsonDecode(json)); checkAPIError(result, 'wrong_device_linking_code'.i18n); // refresh user data after successfully linking device await ffiUserData(); @@ -91,19 +94,14 @@ Future ffiEmailExists(String email) async => await _bindings .cast() .toDartString(); -Pointer ffiRedeemResellerCode(email, currency, deviceName, resellerCode) { - final result = - _bindings.redeemResellerCode(email, currency, deviceName, resellerCode); - // Check for error - // it means you need to check r1 - if (result.r1 != nullptr) { - // Got error throw error to show error ui state - final errorCode = result.r1.cast().toDartString(); - throw PlatformException(code: errorCode, message: 'wrong_seller_code'.i18n); - } +void ffiRedeemResellerCode(email, currency, deviceName, resellerCode) { + final result = _bindings + .redeemResellerCode(email, currency, deviceName, resellerCode) + .cast() + .toDartString(); + checkAPIError(result, 'wrong_seller_code'.i18n); // if successful redeeming a reseller code, immediately refresh Pro user data ffiProUser(); - return result.r0.cast(); } Pointer ffiReferral() => _bindings.referral().cast(); diff --git a/lib/plans/checkout.dart b/lib/plans/checkout.dart index 05a6ea6d8..f61e5b594 100644 --- a/lib/plans/checkout.dart +++ b/lib/plans/checkout.dart @@ -168,7 +168,7 @@ class _CheckoutState extends State "stripe", os, ); - switch (Platform.operatingSystem) { + switch (os) { case 'windows': await AppBrowser.openWindowsWebview(redirectUrl); break; diff --git a/lib/vpn/protos_shared/vpn.pb.dart b/lib/vpn/protos_shared/vpn.pb.dart index 435805715..1b3ced5ad 100644 --- a/lib/vpn/protos_shared/vpn.pb.dart +++ b/lib/vpn/protos_shared/vpn.pb.dart @@ -405,6 +405,7 @@ class Plan extends $pb.GeneratedMessage { $core.bool? bestValue, $fixnum.Int64? usdPrice, $core.Map<$core.String, $fixnum.Int64>? price, + $core.Map<$core.String, $fixnum.Int64>? expectedMonthlyPrice, $core.String? totalCostBilledOneTime, $core.String? oneMonthCost, $core.String? totalCost, @@ -427,6 +428,9 @@ class Plan extends $pb.GeneratedMessage { if (price != null) { $result.price.addAll(price); } + if (expectedMonthlyPrice != null) { + $result.expectedMonthlyPrice.addAll(expectedMonthlyPrice); + } if (totalCostBilledOneTime != null) { $result.totalCostBilledOneTime = totalCostBilledOneTime; } @@ -454,11 +458,12 @@ class Plan extends $pb.GeneratedMessage { ..aOB(3, _omitFieldNames ? '' : 'bestValue', protoName: 'bestValue') ..aInt64(4, _omitFieldNames ? '' : 'usdPrice', protoName: 'usdPrice') ..m<$core.String, $fixnum.Int64>(5, _omitFieldNames ? '' : 'price', entryClassName: 'Plan.PriceEntry', keyFieldType: $pb.PbFieldType.OS, valueFieldType: $pb.PbFieldType.O6) - ..aOS(6, _omitFieldNames ? '' : 'totalCostBilledOneTime', protoName: 'totalCostBilledOneTime') - ..aOS(7, _omitFieldNames ? '' : 'oneMonthCost', protoName: 'oneMonthCost') - ..aOS(8, _omitFieldNames ? '' : 'totalCost', protoName: 'totalCost') - ..aOS(9, _omitFieldNames ? '' : 'formattedBonus', protoName: 'formattedBonus') - ..aOS(10, _omitFieldNames ? '' : 'renewalText', protoName: 'renewalText') + ..m<$core.String, $fixnum.Int64>(6, _omitFieldNames ? '' : 'expectedMonthlyPrice', protoName: 'expectedMonthlyPrice', entryClassName: 'Plan.ExpectedMonthlyPriceEntry', keyFieldType: $pb.PbFieldType.OS, valueFieldType: $pb.PbFieldType.O6) + ..aOS(7, _omitFieldNames ? '' : 'totalCostBilledOneTime', protoName: 'totalCostBilledOneTime') + ..aOS(8, _omitFieldNames ? '' : 'oneMonthCost', protoName: 'oneMonthCost') + ..aOS(9, _omitFieldNames ? '' : 'totalCost', protoName: 'totalCost') + ..aOS(10, _omitFieldNames ? '' : 'formattedBonus', protoName: 'formattedBonus') + ..aOS(11, _omitFieldNames ? '' : 'renewalText', protoName: 'renewalText') ..hasRequiredFields = false ; @@ -523,60 +528,67 @@ class Plan extends $pb.GeneratedMessage { $core.Map<$core.String, $fixnum.Int64> get price => $_getMap(4); @$pb.TagNumber(6) - $core.String get totalCostBilledOneTime => $_getSZ(5); - @$pb.TagNumber(6) - set totalCostBilledOneTime($core.String v) { $_setString(5, v); } - @$pb.TagNumber(6) - $core.bool hasTotalCostBilledOneTime() => $_has(5); - @$pb.TagNumber(6) - void clearTotalCostBilledOneTime() => clearField(6); + $core.Map<$core.String, $fixnum.Int64> get expectedMonthlyPrice => $_getMap(5); @$pb.TagNumber(7) - $core.String get oneMonthCost => $_getSZ(6); + $core.String get totalCostBilledOneTime => $_getSZ(6); @$pb.TagNumber(7) - set oneMonthCost($core.String v) { $_setString(6, v); } + set totalCostBilledOneTime($core.String v) { $_setString(6, v); } @$pb.TagNumber(7) - $core.bool hasOneMonthCost() => $_has(6); + $core.bool hasTotalCostBilledOneTime() => $_has(6); @$pb.TagNumber(7) - void clearOneMonthCost() => clearField(7); + void clearTotalCostBilledOneTime() => clearField(7); @$pb.TagNumber(8) - $core.String get totalCost => $_getSZ(7); + $core.String get oneMonthCost => $_getSZ(7); @$pb.TagNumber(8) - set totalCost($core.String v) { $_setString(7, v); } + set oneMonthCost($core.String v) { $_setString(7, v); } @$pb.TagNumber(8) - $core.bool hasTotalCost() => $_has(7); + $core.bool hasOneMonthCost() => $_has(7); @$pb.TagNumber(8) - void clearTotalCost() => clearField(8); + void clearOneMonthCost() => clearField(8); @$pb.TagNumber(9) - $core.String get formattedBonus => $_getSZ(8); + $core.String get totalCost => $_getSZ(8); @$pb.TagNumber(9) - set formattedBonus($core.String v) { $_setString(8, v); } + set totalCost($core.String v) { $_setString(8, v); } @$pb.TagNumber(9) - $core.bool hasFormattedBonus() => $_has(8); + $core.bool hasTotalCost() => $_has(8); @$pb.TagNumber(9) - void clearFormattedBonus() => clearField(9); + void clearTotalCost() => clearField(9); @$pb.TagNumber(10) - $core.String get renewalText => $_getSZ(9); + $core.String get formattedBonus => $_getSZ(9); @$pb.TagNumber(10) - set renewalText($core.String v) { $_setString(9, v); } + set formattedBonus($core.String v) { $_setString(9, v); } @$pb.TagNumber(10) - $core.bool hasRenewalText() => $_has(9); + $core.bool hasFormattedBonus() => $_has(9); @$pb.TagNumber(10) - void clearRenewalText() => clearField(10); + void clearFormattedBonus() => clearField(10); + + @$pb.TagNumber(11) + $core.String get renewalText => $_getSZ(10); + @$pb.TagNumber(11) + set renewalText($core.String v) { $_setString(10, v); } + @$pb.TagNumber(11) + $core.bool hasRenewalText() => $_has(10); + @$pb.TagNumber(11) + void clearRenewalText() => clearField(11); } class PaymentProviders extends $pb.GeneratedMessage { factory PaymentProviders({ $core.String? name, + $core.Map<$core.String, $core.String>? data, $core.Iterable<$core.String>? logoUrls, }) { final $result = create(); if (name != null) { $result.name = name; } + if (data != null) { + $result.data.addAll(data); + } if (logoUrls != null) { $result.logoUrls.addAll(logoUrls); } @@ -588,7 +600,8 @@ class PaymentProviders extends $pb.GeneratedMessage { static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'PaymentProviders', createEmptyInstance: create) ..aOS(1, _omitFieldNames ? '' : 'name') - ..pPS(2, _omitFieldNames ? '' : 'logoUrls', protoName: 'logoUrls') + ..m<$core.String, $core.String>(2, _omitFieldNames ? '' : 'data', entryClassName: 'PaymentProviders.DataEntry', keyFieldType: $pb.PbFieldType.OS, valueFieldType: $pb.PbFieldType.OS) + ..pPS(3, _omitFieldNames ? '' : 'logoUrls', protoName: 'logoUrls') ..hasRequiredFields = false ; @@ -623,7 +636,10 @@ class PaymentProviders extends $pb.GeneratedMessage { void clearName() => clearField(1); @$pb.TagNumber(2) - $core.List<$core.String> get logoUrls => $_getList(1); + $core.Map<$core.String, $core.String> get data => $_getMap(1); + + @$pb.TagNumber(3) + $core.List<$core.String> get logoUrls => $_getList(2); } class PaymentMethod extends $pb.GeneratedMessage { @@ -690,6 +706,7 @@ class User extends $pb.GeneratedMessage { $core.String? email, $core.String? telephone, $core.String? userStatus, + $core.String? userLevel, $core.String? locale, $fixnum.Int64? expiration, $core.Iterable? devices, @@ -712,6 +729,9 @@ class User extends $pb.GeneratedMessage { if (userStatus != null) { $result.userStatus = userStatus; } + if (userLevel != null) { + $result.userLevel = userLevel; + } if (locale != null) { $result.locale = locale; } @@ -747,14 +767,15 @@ class User extends $pb.GeneratedMessage { ..aOS(2, _omitFieldNames ? '' : 'email') ..aOS(3, _omitFieldNames ? '' : 'telephone') ..aOS(4, _omitFieldNames ? '' : 'userStatus', protoName: 'userStatus') - ..aOS(5, _omitFieldNames ? '' : 'locale') - ..aInt64(6, _omitFieldNames ? '' : 'expiration') - ..pc(7, _omitFieldNames ? '' : 'devices', $pb.PbFieldType.PM, subBuilder: Device.create) - ..aOS(8, _omitFieldNames ? '' : 'code') - ..aInt64(9, _omitFieldNames ? '' : 'expireAt', protoName: 'expireAt') - ..aOS(10, _omitFieldNames ? '' : 'referral') - ..aOS(11, _omitFieldNames ? '' : 'token') - ..aOB(12, _omitFieldNames ? '' : 'yinbiEnabled', protoName: 'yinbiEnabled') + ..aOS(5, _omitFieldNames ? '' : 'userLevel', protoName: 'userLevel') + ..aOS(6, _omitFieldNames ? '' : 'locale') + ..aInt64(7, _omitFieldNames ? '' : 'expiration') + ..pc(8, _omitFieldNames ? '' : 'devices', $pb.PbFieldType.PM, subBuilder: Device.create) + ..aOS(9, _omitFieldNames ? '' : 'code') + ..aInt64(10, _omitFieldNames ? '' : 'expireAt', protoName: 'expireAt') + ..aOS(11, _omitFieldNames ? '' : 'referral') + ..aOS(12, _omitFieldNames ? '' : 'token') + ..aOB(13, _omitFieldNames ? '' : 'yinbiEnabled', protoName: 'yinbiEnabled') ..hasRequiredFields = false ; @@ -816,75 +837,84 @@ class User extends $pb.GeneratedMessage { void clearUserStatus() => clearField(4); @$pb.TagNumber(5) - $core.String get locale => $_getSZ(4); + $core.String get userLevel => $_getSZ(4); @$pb.TagNumber(5) - set locale($core.String v) { $_setString(4, v); } + set userLevel($core.String v) { $_setString(4, v); } @$pb.TagNumber(5) - $core.bool hasLocale() => $_has(4); + $core.bool hasUserLevel() => $_has(4); @$pb.TagNumber(5) - void clearLocale() => clearField(5); + void clearUserLevel() => clearField(5); @$pb.TagNumber(6) - $fixnum.Int64 get expiration => $_getI64(5); + $core.String get locale => $_getSZ(5); @$pb.TagNumber(6) - set expiration($fixnum.Int64 v) { $_setInt64(5, v); } + set locale($core.String v) { $_setString(5, v); } @$pb.TagNumber(6) - $core.bool hasExpiration() => $_has(5); + $core.bool hasLocale() => $_has(5); @$pb.TagNumber(6) - void clearExpiration() => clearField(6); + void clearLocale() => clearField(6); @$pb.TagNumber(7) - $core.List get devices => $_getList(6); + $fixnum.Int64 get expiration => $_getI64(6); + @$pb.TagNumber(7) + set expiration($fixnum.Int64 v) { $_setInt64(6, v); } + @$pb.TagNumber(7) + $core.bool hasExpiration() => $_has(6); + @$pb.TagNumber(7) + void clearExpiration() => clearField(7); @$pb.TagNumber(8) - $core.String get code => $_getSZ(7); - @$pb.TagNumber(8) - set code($core.String v) { $_setString(7, v); } - @$pb.TagNumber(8) - $core.bool hasCode() => $_has(7); - @$pb.TagNumber(8) - void clearCode() => clearField(8); + $core.List get devices => $_getList(7); @$pb.TagNumber(9) - $fixnum.Int64 get expireAt => $_getI64(8); + $core.String get code => $_getSZ(8); @$pb.TagNumber(9) - set expireAt($fixnum.Int64 v) { $_setInt64(8, v); } + set code($core.String v) { $_setString(8, v); } @$pb.TagNumber(9) - $core.bool hasExpireAt() => $_has(8); + $core.bool hasCode() => $_has(8); @$pb.TagNumber(9) - void clearExpireAt() => clearField(9); + void clearCode() => clearField(9); @$pb.TagNumber(10) - $core.String get referral => $_getSZ(9); + $fixnum.Int64 get expireAt => $_getI64(9); @$pb.TagNumber(10) - set referral($core.String v) { $_setString(9, v); } + set expireAt($fixnum.Int64 v) { $_setInt64(9, v); } @$pb.TagNumber(10) - $core.bool hasReferral() => $_has(9); + $core.bool hasExpireAt() => $_has(9); @$pb.TagNumber(10) - void clearReferral() => clearField(10); + void clearExpireAt() => clearField(10); @$pb.TagNumber(11) - $core.String get token => $_getSZ(10); + $core.String get referral => $_getSZ(10); @$pb.TagNumber(11) - set token($core.String v) { $_setString(10, v); } + set referral($core.String v) { $_setString(10, v); } @$pb.TagNumber(11) - $core.bool hasToken() => $_has(10); + $core.bool hasReferral() => $_has(10); @$pb.TagNumber(11) - void clearToken() => clearField(11); + void clearReferral() => clearField(11); @$pb.TagNumber(12) - $core.bool get yinbiEnabled => $_getBF(11); + $core.String get token => $_getSZ(11); @$pb.TagNumber(12) - set yinbiEnabled($core.bool v) { $_setBool(11, v); } + set token($core.String v) { $_setString(11, v); } @$pb.TagNumber(12) - $core.bool hasYinbiEnabled() => $_has(11); + $core.bool hasToken() => $_has(11); @$pb.TagNumber(12) - void clearYinbiEnabled() => clearField(12); + void clearToken() => clearField(12); + + @$pb.TagNumber(13) + $core.bool get yinbiEnabled => $_getBF(12); + @$pb.TagNumber(13) + set yinbiEnabled($core.bool v) { $_setBool(12, v); } + @$pb.TagNumber(13) + $core.bool hasYinbiEnabled() => $_has(12); + @$pb.TagNumber(13) + void clearYinbiEnabled() => clearField(13); } /// API -class APIResponse extends $pb.GeneratedMessage { - factory APIResponse({ +class BaseResponse extends $pb.GeneratedMessage { + factory BaseResponse({ $core.String? status, $core.String? error, $core.String? errorId, @@ -901,11 +931,11 @@ class APIResponse extends $pb.GeneratedMessage { } return $result; } - APIResponse._() : super(); - factory APIResponse.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); - factory APIResponse.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + BaseResponse._() : super(); + factory BaseResponse.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory BaseResponse.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); - static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'APIResponse', createEmptyInstance: create) + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'BaseResponse', createEmptyInstance: create) ..aOS(1, _omitFieldNames ? '' : 'status') ..aOS(2, _omitFieldNames ? '' : 'error') ..aOS(3, _omitFieldNames ? '' : 'errorId', protoName: 'errorId') @@ -916,22 +946,22 @@ class APIResponse extends $pb.GeneratedMessage { 'Using this can add significant overhead to your binary. ' 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' 'Will be removed in next major version') - APIResponse clone() => APIResponse()..mergeFromMessage(this); + BaseResponse clone() => BaseResponse()..mergeFromMessage(this); @$core.Deprecated( 'Using this can add significant overhead to your binary. ' 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' 'Will be removed in next major version') - APIResponse copyWith(void Function(APIResponse) updates) => super.copyWith((message) => updates(message as APIResponse)) as APIResponse; + BaseResponse copyWith(void Function(BaseResponse) updates) => super.copyWith((message) => updates(message as BaseResponse)) as BaseResponse; $pb.BuilderInfo get info_ => _i; @$core.pragma('dart2js:noInline') - static APIResponse create() => APIResponse._(); - APIResponse createEmptyInstance() => create(); - static $pb.PbList createRepeated() => $pb.PbList(); + static BaseResponse create() => BaseResponse._(); + BaseResponse createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); @$core.pragma('dart2js:noInline') - static APIResponse getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); - static APIResponse? _defaultInstance; + static BaseResponse getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static BaseResponse? _defaultInstance; @$pb.TagNumber(1) $core.String get status => $_getSZ(0); @@ -961,6 +991,260 @@ class APIResponse extends $pb.GeneratedMessage { void clearErrorId() => clearField(3); } +class PaymentRedirectRequest extends $pb.GeneratedMessage { + factory PaymentRedirectRequest({ + $core.String? plan, + $core.String? provider, + $core.String? currency, + $core.String? email, + $core.String? deviceName, + $core.String? countryCode, + $core.String? locale, + }) { + final $result = create(); + if (plan != null) { + $result.plan = plan; + } + if (provider != null) { + $result.provider = provider; + } + if (currency != null) { + $result.currency = currency; + } + if (email != null) { + $result.email = email; + } + if (deviceName != null) { + $result.deviceName = deviceName; + } + if (countryCode != null) { + $result.countryCode = countryCode; + } + if (locale != null) { + $result.locale = locale; + } + return $result; + } + PaymentRedirectRequest._() : super(); + factory PaymentRedirectRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory PaymentRedirectRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'PaymentRedirectRequest', createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'plan') + ..aOS(2, _omitFieldNames ? '' : 'provider') + ..aOS(3, _omitFieldNames ? '' : 'currency') + ..aOS(4, _omitFieldNames ? '' : 'email') + ..aOS(5, _omitFieldNames ? '' : 'deviceName', protoName: 'deviceName') + ..aOS(6, _omitFieldNames ? '' : 'countryCode', protoName: 'countryCode') + ..aOS(7, _omitFieldNames ? '' : 'locale') + ..hasRequiredFields = false + ; + + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + PaymentRedirectRequest clone() => PaymentRedirectRequest()..mergeFromMessage(this); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + PaymentRedirectRequest copyWith(void Function(PaymentRedirectRequest) updates) => super.copyWith((message) => updates(message as PaymentRedirectRequest)) as PaymentRedirectRequest; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static PaymentRedirectRequest create() => PaymentRedirectRequest._(); + PaymentRedirectRequest createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static PaymentRedirectRequest getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static PaymentRedirectRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get plan => $_getSZ(0); + @$pb.TagNumber(1) + set plan($core.String v) { $_setString(0, v); } + @$pb.TagNumber(1) + $core.bool hasPlan() => $_has(0); + @$pb.TagNumber(1) + void clearPlan() => clearField(1); + + @$pb.TagNumber(2) + $core.String get provider => $_getSZ(1); + @$pb.TagNumber(2) + set provider($core.String v) { $_setString(1, v); } + @$pb.TagNumber(2) + $core.bool hasProvider() => $_has(1); + @$pb.TagNumber(2) + void clearProvider() => clearField(2); + + @$pb.TagNumber(3) + $core.String get currency => $_getSZ(2); + @$pb.TagNumber(3) + set currency($core.String v) { $_setString(2, v); } + @$pb.TagNumber(3) + $core.bool hasCurrency() => $_has(2); + @$pb.TagNumber(3) + void clearCurrency() => clearField(3); + + @$pb.TagNumber(4) + $core.String get email => $_getSZ(3); + @$pb.TagNumber(4) + set email($core.String v) { $_setString(3, v); } + @$pb.TagNumber(4) + $core.bool hasEmail() => $_has(3); + @$pb.TagNumber(4) + void clearEmail() => clearField(4); + + @$pb.TagNumber(5) + $core.String get deviceName => $_getSZ(4); + @$pb.TagNumber(5) + set deviceName($core.String v) { $_setString(4, v); } + @$pb.TagNumber(5) + $core.bool hasDeviceName() => $_has(4); + @$pb.TagNumber(5) + void clearDeviceName() => clearField(5); + + @$pb.TagNumber(6) + $core.String get countryCode => $_getSZ(5); + @$pb.TagNumber(6) + set countryCode($core.String v) { $_setString(5, v); } + @$pb.TagNumber(6) + $core.bool hasCountryCode() => $_has(5); + @$pb.TagNumber(6) + void clearCountryCode() => clearField(6); + + @$pb.TagNumber(7) + $core.String get locale => $_getSZ(6); + @$pb.TagNumber(7) + set locale($core.String v) { $_setString(6, v); } + @$pb.TagNumber(7) + $core.bool hasLocale() => $_has(6); + @$pb.TagNumber(7) + void clearLocale() => clearField(7); +} + +class RedeemResellerCodeRequest extends $pb.GeneratedMessage { + factory RedeemResellerCodeRequest({ + $core.String? email, + $core.String? resellerCode, + $core.String? deviceName, + $core.String? currency, + $core.String? idempotencyKey, + $core.String? provider, + }) { + final $result = create(); + if (email != null) { + $result.email = email; + } + if (resellerCode != null) { + $result.resellerCode = resellerCode; + } + if (deviceName != null) { + $result.deviceName = deviceName; + } + if (currency != null) { + $result.currency = currency; + } + if (idempotencyKey != null) { + $result.idempotencyKey = idempotencyKey; + } + if (provider != null) { + $result.provider = provider; + } + return $result; + } + RedeemResellerCodeRequest._() : super(); + factory RedeemResellerCodeRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory RedeemResellerCodeRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'RedeemResellerCodeRequest', createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'email') + ..aOS(2, _omitFieldNames ? '' : 'resellerCode', protoName: 'resellerCode') + ..aOS(3, _omitFieldNames ? '' : 'deviceName', protoName: 'deviceName') + ..aOS(4, _omitFieldNames ? '' : 'currency') + ..aOS(5, _omitFieldNames ? '' : 'idempotencyKey', protoName: 'idempotencyKey') + ..aOS(6, _omitFieldNames ? '' : 'provider') + ..hasRequiredFields = false + ; + + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + RedeemResellerCodeRequest clone() => RedeemResellerCodeRequest()..mergeFromMessage(this); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + RedeemResellerCodeRequest copyWith(void Function(RedeemResellerCodeRequest) updates) => super.copyWith((message) => updates(message as RedeemResellerCodeRequest)) as RedeemResellerCodeRequest; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static RedeemResellerCodeRequest create() => RedeemResellerCodeRequest._(); + RedeemResellerCodeRequest createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static RedeemResellerCodeRequest getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static RedeemResellerCodeRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get email => $_getSZ(0); + @$pb.TagNumber(1) + set email($core.String v) { $_setString(0, v); } + @$pb.TagNumber(1) + $core.bool hasEmail() => $_has(0); + @$pb.TagNumber(1) + void clearEmail() => clearField(1); + + @$pb.TagNumber(2) + $core.String get resellerCode => $_getSZ(1); + @$pb.TagNumber(2) + set resellerCode($core.String v) { $_setString(1, v); } + @$pb.TagNumber(2) + $core.bool hasResellerCode() => $_has(1); + @$pb.TagNumber(2) + void clearResellerCode() => clearField(2); + + @$pb.TagNumber(3) + $core.String get deviceName => $_getSZ(2); + @$pb.TagNumber(3) + set deviceName($core.String v) { $_setString(2, v); } + @$pb.TagNumber(3) + $core.bool hasDeviceName() => $_has(2); + @$pb.TagNumber(3) + void clearDeviceName() => clearField(3); + + @$pb.TagNumber(4) + $core.String get currency => $_getSZ(3); + @$pb.TagNumber(4) + set currency($core.String v) { $_setString(3, v); } + @$pb.TagNumber(4) + $core.bool hasCurrency() => $_has(3); + @$pb.TagNumber(4) + void clearCurrency() => clearField(4); + + @$pb.TagNumber(5) + $core.String get idempotencyKey => $_getSZ(4); + @$pb.TagNumber(5) + set idempotencyKey($core.String v) { $_setString(4, v); } + @$pb.TagNumber(5) + $core.bool hasIdempotencyKey() => $_has(4); + @$pb.TagNumber(5) + void clearIdempotencyKey() => clearField(5); + + @$pb.TagNumber(6) + $core.String get provider => $_getSZ(5); + @$pb.TagNumber(6) + set provider($core.String v) { $_setString(5, v); } + @$pb.TagNumber(6) + $core.bool hasProvider() => $_has(5); + @$pb.TagNumber(6) + void clearProvider() => clearField(6); +} + class PaymentRedirectResponse extends $pb.GeneratedMessage { factory PaymentRedirectResponse({ $core.String? status, diff --git a/lib/vpn/protos_shared/vpn.pbjson.dart b/lib/vpn/protos_shared/vpn.pbjson.dart index e050df112..4e537294d 100644 --- a/lib/vpn/protos_shared/vpn.pbjson.dart +++ b/lib/vpn/protos_shared/vpn.pbjson.dart @@ -98,13 +98,14 @@ const Plan$json = { {'1': 'bestValue', '3': 3, '4': 1, '5': 8, '10': 'bestValue'}, {'1': 'usdPrice', '3': 4, '4': 1, '5': 3, '10': 'usdPrice'}, {'1': 'price', '3': 5, '4': 3, '5': 11, '6': '.Plan.PriceEntry', '10': 'price'}, - {'1': 'totalCostBilledOneTime', '3': 6, '4': 1, '5': 9, '10': 'totalCostBilledOneTime'}, - {'1': 'oneMonthCost', '3': 7, '4': 1, '5': 9, '10': 'oneMonthCost'}, - {'1': 'totalCost', '3': 8, '4': 1, '5': 9, '10': 'totalCost'}, - {'1': 'formattedBonus', '3': 9, '4': 1, '5': 9, '10': 'formattedBonus'}, - {'1': 'renewalText', '3': 10, '4': 1, '5': 9, '10': 'renewalText'}, + {'1': 'expectedMonthlyPrice', '3': 6, '4': 3, '5': 11, '6': '.Plan.ExpectedMonthlyPriceEntry', '10': 'expectedMonthlyPrice'}, + {'1': 'totalCostBilledOneTime', '3': 7, '4': 1, '5': 9, '10': 'totalCostBilledOneTime'}, + {'1': 'oneMonthCost', '3': 8, '4': 1, '5': 9, '10': 'oneMonthCost'}, + {'1': 'totalCost', '3': 9, '4': 1, '5': 9, '10': 'totalCost'}, + {'1': 'formattedBonus', '3': 10, '4': 1, '5': 9, '10': 'formattedBonus'}, + {'1': 'renewalText', '3': 11, '4': 1, '5': 9, '10': 'renewalText'}, ], - '3': [Plan_PriceEntry$json], + '3': [Plan_PriceEntry$json, Plan_ExpectedMonthlyPriceEntry$json], }; @$core.Deprecated('Use planDescriptor instead') @@ -117,30 +118,57 @@ const Plan_PriceEntry$json = { '7': {'7': true}, }; +@$core.Deprecated('Use planDescriptor instead') +const Plan_ExpectedMonthlyPriceEntry$json = { + '1': 'ExpectedMonthlyPriceEntry', + '2': [ + {'1': 'key', '3': 1, '4': 1, '5': 9, '10': 'key'}, + {'1': 'value', '3': 2, '4': 1, '5': 3, '10': 'value'}, + ], + '7': {'7': true}, +}; + /// Descriptor for `Plan`. Decode as a `google.protobuf.DescriptorProto`. final $typed_data.Uint8List planDescriptor = $convert.base64Decode( 'CgRQbGFuEg4KAmlkGAEgASgJUgJpZBIgCgtkZXNjcmlwdGlvbhgCIAEoCVILZGVzY3JpcHRpb2' '4SHAoJYmVzdFZhbHVlGAMgASgIUgliZXN0VmFsdWUSGgoIdXNkUHJpY2UYBCABKANSCHVzZFBy' - 'aWNlEiYKBXByaWNlGAUgAygLMhAuUGxhbi5QcmljZUVudHJ5UgVwcmljZRI2ChZ0b3RhbENvc3' - 'RCaWxsZWRPbmVUaW1lGAYgASgJUhZ0b3RhbENvc3RCaWxsZWRPbmVUaW1lEiIKDG9uZU1vbnRo' - 'Q29zdBgHIAEoCVIMb25lTW9udGhDb3N0EhwKCXRvdGFsQ29zdBgIIAEoCVIJdG90YWxDb3N0Ei' - 'YKDmZvcm1hdHRlZEJvbnVzGAkgASgJUg5mb3JtYXR0ZWRCb251cxIgCgtyZW5ld2FsVGV4dBgK' - 'IAEoCVILcmVuZXdhbFRleHQaOAoKUHJpY2VFbnRyeRIQCgNrZXkYASABKAlSA2tleRIUCgV2YW' - 'x1ZRgCIAEoA1IFdmFsdWU6AjgB'); + 'aWNlEiYKBXByaWNlGAUgAygLMhAuUGxhbi5QcmljZUVudHJ5UgVwcmljZRJTChRleHBlY3RlZE' + '1vbnRobHlQcmljZRgGIAMoCzIfLlBsYW4uRXhwZWN0ZWRNb250aGx5UHJpY2VFbnRyeVIUZXhw' + 'ZWN0ZWRNb250aGx5UHJpY2USNgoWdG90YWxDb3N0QmlsbGVkT25lVGltZRgHIAEoCVIWdG90YW' + 'xDb3N0QmlsbGVkT25lVGltZRIiCgxvbmVNb250aENvc3QYCCABKAlSDG9uZU1vbnRoQ29zdBIc' + 'Cgl0b3RhbENvc3QYCSABKAlSCXRvdGFsQ29zdBImCg5mb3JtYXR0ZWRCb251cxgKIAEoCVIOZm' + '9ybWF0dGVkQm9udXMSIAoLcmVuZXdhbFRleHQYCyABKAlSC3JlbmV3YWxUZXh0GjgKClByaWNl' + 'RW50cnkSEAoDa2V5GAEgASgJUgNrZXkSFAoFdmFsdWUYAiABKANSBXZhbHVlOgI4ARpHChlFeH' + 'BlY3RlZE1vbnRobHlQcmljZUVudHJ5EhAKA2tleRgBIAEoCVIDa2V5EhQKBXZhbHVlGAIgASgD' + 'UgV2YWx1ZToCOAE='); @$core.Deprecated('Use paymentProvidersDescriptor instead') const PaymentProviders$json = { '1': 'PaymentProviders', '2': [ {'1': 'name', '3': 1, '4': 1, '5': 9, '10': 'name'}, - {'1': 'logoUrls', '3': 2, '4': 3, '5': 9, '10': 'logoUrls'}, + {'1': 'data', '3': 2, '4': 3, '5': 11, '6': '.PaymentProviders.DataEntry', '10': 'data'}, + {'1': 'logoUrls', '3': 3, '4': 3, '5': 9, '10': 'logoUrls'}, ], + '3': [PaymentProviders_DataEntry$json], +}; + +@$core.Deprecated('Use paymentProvidersDescriptor instead') +const PaymentProviders_DataEntry$json = { + '1': 'DataEntry', + '2': [ + {'1': 'key', '3': 1, '4': 1, '5': 9, '10': 'key'}, + {'1': 'value', '3': 2, '4': 1, '5': 9, '10': 'value'}, + ], + '7': {'7': true}, }; /// Descriptor for `PaymentProviders`. Decode as a `google.protobuf.DescriptorProto`. final $typed_data.Uint8List paymentProvidersDescriptor = $convert.base64Decode( - 'ChBQYXltZW50UHJvdmlkZXJzEhIKBG5hbWUYASABKAlSBG5hbWUSGgoIbG9nb1VybHMYAiADKA' - 'lSCGxvZ29Vcmxz'); + 'ChBQYXltZW50UHJvdmlkZXJzEhIKBG5hbWUYASABKAlSBG5hbWUSLwoEZGF0YRgCIAMoCzIbLl' + 'BheW1lbnRQcm92aWRlcnMuRGF0YUVudHJ5UgRkYXRhEhoKCGxvZ29VcmxzGAMgAygJUghsb2dv' + 'VXJscxo3CglEYXRhRW50cnkSEAoDa2V5GAEgASgJUgNrZXkSFAoFdmFsdWUYAiABKAlSBXZhbH' + 'VlOgI4AQ=='); @$core.Deprecated('Use paymentMethodDescriptor instead') const PaymentMethod$json = { @@ -164,14 +192,15 @@ const User$json = { {'1': 'email', '3': 2, '4': 1, '5': 9, '10': 'email'}, {'1': 'telephone', '3': 3, '4': 1, '5': 9, '10': 'telephone'}, {'1': 'userStatus', '3': 4, '4': 1, '5': 9, '10': 'userStatus'}, - {'1': 'locale', '3': 5, '4': 1, '5': 9, '10': 'locale'}, - {'1': 'expiration', '3': 6, '4': 1, '5': 3, '10': 'expiration'}, - {'1': 'devices', '3': 7, '4': 3, '5': 11, '6': '.Device', '10': 'devices'}, - {'1': 'code', '3': 8, '4': 1, '5': 9, '10': 'code'}, - {'1': 'expireAt', '3': 9, '4': 1, '5': 3, '10': 'expireAt'}, - {'1': 'referral', '3': 10, '4': 1, '5': 9, '10': 'referral'}, - {'1': 'token', '3': 11, '4': 1, '5': 9, '10': 'token'}, - {'1': 'yinbiEnabled', '3': 12, '4': 1, '5': 8, '10': 'yinbiEnabled'}, + {'1': 'userLevel', '3': 5, '4': 1, '5': 9, '10': 'userLevel'}, + {'1': 'locale', '3': 6, '4': 1, '5': 9, '10': 'locale'}, + {'1': 'expiration', '3': 7, '4': 1, '5': 3, '10': 'expiration'}, + {'1': 'devices', '3': 8, '4': 3, '5': 11, '6': '.Device', '10': 'devices'}, + {'1': 'code', '3': 9, '4': 1, '5': 9, '10': 'code'}, + {'1': 'expireAt', '3': 10, '4': 1, '5': 3, '10': 'expireAt'}, + {'1': 'referral', '3': 11, '4': 1, '5': 9, '10': 'referral'}, + {'1': 'token', '3': 12, '4': 1, '5': 9, '10': 'token'}, + {'1': 'yinbiEnabled', '3': 13, '4': 1, '5': 8, '10': 'yinbiEnabled'}, ], }; @@ -179,15 +208,15 @@ const User$json = { final $typed_data.Uint8List userDescriptor = $convert.base64Decode( 'CgRVc2VyEhYKBnVzZXJJZBgBIAEoA1IGdXNlcklkEhQKBWVtYWlsGAIgASgJUgVlbWFpbBIcCg' 'l0ZWxlcGhvbmUYAyABKAlSCXRlbGVwaG9uZRIeCgp1c2VyU3RhdHVzGAQgASgJUgp1c2VyU3Rh' - 'dHVzEhYKBmxvY2FsZRgFIAEoCVIGbG9jYWxlEh4KCmV4cGlyYXRpb24YBiABKANSCmV4cGlyYX' - 'Rpb24SIQoHZGV2aWNlcxgHIAMoCzIHLkRldmljZVIHZGV2aWNlcxISCgRjb2RlGAggASgJUgRj' - 'b2RlEhoKCGV4cGlyZUF0GAkgASgDUghleHBpcmVBdBIaCghyZWZlcnJhbBgKIAEoCVIIcmVmZX' - 'JyYWwSFAoFdG9rZW4YCyABKAlSBXRva2VuEiIKDHlpbmJpRW5hYmxlZBgMIAEoCFIMeWluYmlF' - 'bmFibGVk'); + 'dHVzEhwKCXVzZXJMZXZlbBgFIAEoCVIJdXNlckxldmVsEhYKBmxvY2FsZRgGIAEoCVIGbG9jYW' + 'xlEh4KCmV4cGlyYXRpb24YByABKANSCmV4cGlyYXRpb24SIQoHZGV2aWNlcxgIIAMoCzIHLkRl' + 'dmljZVIHZGV2aWNlcxISCgRjb2RlGAkgASgJUgRjb2RlEhoKCGV4cGlyZUF0GAogASgDUghleH' + 'BpcmVBdBIaCghyZWZlcnJhbBgLIAEoCVIIcmVmZXJyYWwSFAoFdG9rZW4YDCABKAlSBXRva2Vu' + 'EiIKDHlpbmJpRW5hYmxlZBgNIAEoCFIMeWluYmlFbmFibGVk'); -@$core.Deprecated('Use aPIResponseDescriptor instead') -const APIResponse$json = { - '1': 'APIResponse', +@$core.Deprecated('Use baseResponseDescriptor instead') +const BaseResponse$json = { + '1': 'BaseResponse', '2': [ {'1': 'status', '3': 1, '4': 1, '5': 9, '10': 'status'}, {'1': 'error', '3': 2, '4': 1, '5': 9, '10': 'error'}, @@ -195,10 +224,51 @@ const APIResponse$json = { ], }; -/// Descriptor for `APIResponse`. Decode as a `google.protobuf.DescriptorProto`. -final $typed_data.Uint8List aPIResponseDescriptor = $convert.base64Decode( - 'CgtBUElSZXNwb25zZRIWCgZzdGF0dXMYASABKAlSBnN0YXR1cxIUCgVlcnJvchgCIAEoCVIFZX' - 'Jyb3ISGAoHZXJyb3JJZBgDIAEoCVIHZXJyb3JJZA=='); +/// Descriptor for `BaseResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List baseResponseDescriptor = $convert.base64Decode( + 'CgxCYXNlUmVzcG9uc2USFgoGc3RhdHVzGAEgASgJUgZzdGF0dXMSFAoFZXJyb3IYAiABKAlSBW' + 'Vycm9yEhgKB2Vycm9ySWQYAyABKAlSB2Vycm9ySWQ='); + +@$core.Deprecated('Use paymentRedirectRequestDescriptor instead') +const PaymentRedirectRequest$json = { + '1': 'PaymentRedirectRequest', + '2': [ + {'1': 'plan', '3': 1, '4': 1, '5': 9, '10': 'plan'}, + {'1': 'provider', '3': 2, '4': 1, '5': 9, '10': 'provider'}, + {'1': 'currency', '3': 3, '4': 1, '5': 9, '10': 'currency'}, + {'1': 'email', '3': 4, '4': 1, '5': 9, '10': 'email'}, + {'1': 'deviceName', '3': 5, '4': 1, '5': 9, '10': 'deviceName'}, + {'1': 'countryCode', '3': 6, '4': 1, '5': 9, '10': 'countryCode'}, + {'1': 'locale', '3': 7, '4': 1, '5': 9, '10': 'locale'}, + ], +}; + +/// Descriptor for `PaymentRedirectRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List paymentRedirectRequestDescriptor = $convert.base64Decode( + 'ChZQYXltZW50UmVkaXJlY3RSZXF1ZXN0EhIKBHBsYW4YASABKAlSBHBsYW4SGgoIcHJvdmlkZX' + 'IYAiABKAlSCHByb3ZpZGVyEhoKCGN1cnJlbmN5GAMgASgJUghjdXJyZW5jeRIUCgVlbWFpbBgE' + 'IAEoCVIFZW1haWwSHgoKZGV2aWNlTmFtZRgFIAEoCVIKZGV2aWNlTmFtZRIgCgtjb3VudHJ5Q2' + '9kZRgGIAEoCVILY291bnRyeUNvZGUSFgoGbG9jYWxlGAcgASgJUgZsb2NhbGU='); + +@$core.Deprecated('Use redeemResellerCodeRequestDescriptor instead') +const RedeemResellerCodeRequest$json = { + '1': 'RedeemResellerCodeRequest', + '2': [ + {'1': 'email', '3': 1, '4': 1, '5': 9, '10': 'email'}, + {'1': 'resellerCode', '3': 2, '4': 1, '5': 9, '10': 'resellerCode'}, + {'1': 'deviceName', '3': 3, '4': 1, '5': 9, '10': 'deviceName'}, + {'1': 'currency', '3': 4, '4': 1, '5': 9, '10': 'currency'}, + {'1': 'idempotencyKey', '3': 5, '4': 1, '5': 9, '10': 'idempotencyKey'}, + {'1': 'provider', '3': 6, '4': 1, '5': 9, '10': 'provider'}, + ], +}; + +/// Descriptor for `RedeemResellerCodeRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List redeemResellerCodeRequestDescriptor = $convert.base64Decode( + 'ChlSZWRlZW1SZXNlbGxlckNvZGVSZXF1ZXN0EhQKBWVtYWlsGAEgASgJUgVlbWFpbBIiCgxyZX' + 'NlbGxlckNvZGUYAiABKAlSDHJlc2VsbGVyQ29kZRIeCgpkZXZpY2VOYW1lGAMgASgJUgpkZXZp' + 'Y2VOYW1lEhoKCGN1cnJlbmN5GAQgASgJUghjdXJyZW5jeRImCg5pZGVtcG90ZW5jeUtleRgFIA' + 'EoCVIOaWRlbXBvdGVuY3lLZXkSGgoIcHJvdmlkZXIYBiABKAlSCHByb3ZpZGVy'); @$core.Deprecated('Use paymentRedirectResponseDescriptor instead') const PaymentRedirectResponse$json = { diff --git a/protos_shared/vpn.proto b/protos_shared/vpn.proto index 7eb485ec6..a49a846eb 100644 --- a/protos_shared/vpn.proto +++ b/protos_shared/vpn.proto @@ -38,16 +38,18 @@ message Plan { bool bestValue = 3; int64 usdPrice = 4; map price = 5; - string totalCostBilledOneTime = 6; - string oneMonthCost = 7; - string totalCost = 8; - string formattedBonus = 9; - string renewalText = 10; + map expectedMonthlyPrice = 6; + string totalCostBilledOneTime = 7; + string oneMonthCost = 8; + string totalCost = 9; + string formattedBonus = 10; + string renewalText = 11; } message PaymentProviders { string name = 1; - repeated string logoUrls = 2; + map data = 2; + repeated string logoUrls = 3; } message PaymentMethod { @@ -60,23 +62,43 @@ message User { string email = 2; string telephone = 3; string userStatus = 4; - string locale = 5; - int64 expiration = 6; - repeated Device devices = 7; - string code = 8; - int64 expireAt = 9; - string referral = 10; - string token = 11; - bool yinbiEnabled = 12; + string userLevel = 5; + string locale = 6; + int64 expiration = 7; + repeated Device devices = 8; + string code = 9; + int64 expireAt = 10; + string referral = 11; + string token = 12; + bool yinbiEnabled = 13; } // API -message APIResponse { +message BaseResponse { string status = 1; string error = 2; string errorId = 3; } +message PaymentRedirectRequest { + string plan = 1; + string provider = 2; + string currency = 3; + string email = 4; + string deviceName = 5; + string countryCode = 6; + string locale = 7; +} + +message RedeemResellerCodeRequest { + string email = 1; + string resellerCode = 2; + string deviceName = 3; + string currency = 4; + string idempotencyKey = 5; + string provider = 6; +} + message PaymentRedirectResponse { string status = 1; string error = 2; diff --git a/pubspec.lock b/pubspec.lock index c450651ff..5d179b66f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -767,10 +767,10 @@ packages: dependency: transitive description: name: frontend_server_client - sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "3.2.0" fuchsia_remote_debug_protocol: dependency: transitive description: flutter @@ -909,6 +909,30 @@ packages: url: "https://pub.dev" source: hosted version: "4.8.1" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + url: "https://pub.dev" + source: hosted + version: "10.0.0" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + url: "https://pub.dev" + source: hosted + version: "2.0.1" lints: dependency: transitive description: @@ -969,10 +993,10 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.8.0" menu_base: dependency: transitive description: @@ -985,10 +1009,10 @@ packages: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.11.0" mime: dependency: "direct main" description: @@ -1582,26 +1606,26 @@ packages: dependency: "direct dev" description: name: test - sha256: "7ee446762c2c50b3bd4ea96fe13ffac69919352bd3b4b17bac3f3465edc58073" + sha256: a1f7595805820fcc05e5c52e3a231aedd0b72972cb333e8c738a8b1239448b6f url: "https://pub.dev" source: hosted - version: "1.25.2" + version: "1.24.9" test_api: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.6.1" test_core: dependency: transitive description: name: test_core - sha256: "2bc4b4ecddd75309300d8096f781c0e3280ca1ef85beda558d33fcbedc2eead4" + sha256: a757b14fc47507060a162cc2530d9a4a2f92f5100a952c7443b5cad5ef5b106a url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.5.9" timezone: dependency: transitive description: @@ -1798,10 +1822,10 @@ packages: dependency: transitive description: name: vm_service - sha256: a13d5503b4facefc515c8c587ce3cf69577a7b064a9f1220e005449cf1f64aad + sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 url: "https://pub.dev" source: hosted - version: "12.0.0" + version: "13.0.0" wakelock: dependency: "direct main" description: