Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 112 additions & 8 deletions backend/radiance.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"go.opentelemetry.io/otel/trace"

C "github.com/getlantern/common"
wire "github.com/getlantern/common/usermessage"
"github.com/getlantern/publicip"

"github.com/getlantern/radiance/account"
Expand All @@ -38,6 +39,7 @@ import (
"github.com/getlantern/radiance/telemetry"
"github.com/getlantern/radiance/traces"
"github.com/getlantern/radiance/unbounded"
clientmessage "github.com/getlantern/radiance/usermessage"
"github.com/getlantern/radiance/vpn"

lbA "github.com/getlantern/lantern-box/adapter"
Expand All @@ -57,6 +59,7 @@ type LocalBackend struct {
confHandler *config.ConfigHandler
issueReporter *issue.IssueReporter
accountClient *account.Client
userMessages *clientmessage.Service

srvManager *servers.Manager
vpnClient *vpn.VPNClient
Expand Down Expand Up @@ -103,8 +106,9 @@ type Options struct {
// own device ID and ignore this value
DeviceID string
// User choice for telemetry consent
TelemetryConsent bool
PlatformInterface vpn.PlatformInterface
TelemetryConsent bool
PlatformInterface vpn.PlatformInterface
UserMessageCapabilities wire.ClientCapabilities
// EnvOverrides are applied via os.Setenv before common.Init so sandboxed
// system extensions (macOS/iOS), which don't inherit shell env, still see
// RADIANCE_* vars from the host process. Entries are set verbatim — no
Expand Down Expand Up @@ -232,13 +236,52 @@ func NewLocalBackend(ctx context.Context, opts Options) (*LocalBackend, error) {
}
r.sessionHistory = vpn.NewSessionHistory(slog.Default().With("service", "session_history"), r.sessionInfo())
r.shutdownFuncs = append(r.shutdownFuncs, func() error { r.sessionHistory.Close(); return nil })
r.userMessages = loadUserMessageService(opts.UserMessageCapabilities, dataDir)
r.clearSelectedIfMissing()
return r, nil
}

func loadUserMessageService(capabilities wire.ClientCapabilities, dataDir string) *clientmessage.Service {
if capabilities.Version == "" {
return nil
}
if err := capabilities.Validate(); err != nil {
slog.Warn("User messages disabled", "reason", "invalid_capabilities")
return nil
}
service, err := clientmessage.New(clientmessage.Options{
DataDir: dataDir,
Fetcher: clientmessage.NewHTTPFetcher(
kindling.HTTPClient(),
clientmessage.Endpoint(common.GetBaseURL()),
capabilities,
),
ContextProvider: func() clientmessage.ClientContext {
return clientmessage.ClientContext{
UserID: settings.GetString(settings.UserIDKey),
ProToken: settings.GetString(settings.TokenKey),
Locale: clientmessage.NormalizeLocale(settings.GetString(settings.LocaleKey)),
Platform: clientmessage.NormalizePlatform(common.Platform),
AppVersion: common.GetVersion(),
}
},
})
if err != nil {
slog.Error("Loading user-message state", "error", err)
return nil
}
return service
}

func (r *LocalBackend) Start() {
// eagerly start kindling so it's ready by the time we need to make network requests
kindling.Init()
if r.userMessages != nil {
events.SubscribeContext(r.ctx, func(account.UserChangeEvent) {
r.userMessages.Refresh()
})
r.userMessages.Start(r.ctx)
}
go func() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
result, err := publicip.Detect(ctx, &publicip.Config{
Expand Down Expand Up @@ -613,6 +656,9 @@ func (r *LocalBackend) PatchSettings(updates settings.Settings) error {
if err := settings.Patch(diff); err != nil {
return fmt.Errorf("failed to update settings: %w", err)
}
if _, ok := diff[settings.LocaleKey]; ok {
r.refreshUserMessages()
}
// telemetry settings
if _, ok := diff[settings.TelemetryKey]; ok {
if settings.GetBool(settings.TelemetryKey) {
Expand Down Expand Up @@ -1567,19 +1613,35 @@ func (r *LocalBackend) RemoveSplitTunnelItems(items vpn.SplitTunnelFilter) error
/////////////

func (r *LocalBackend) NewUser(ctx context.Context) (*account.UserData, error) {
return r.accountClient.NewUser(ctx)
userData, err := r.accountClient.NewUser(ctx)
if err == nil {
r.refreshUserMessages()
}
return userData, err
}

func (r *LocalBackend) Login(ctx context.Context, email, password string) (*account.UserData, error) {
return r.accountClient.Login(ctx, email, password)
userData, err := r.accountClient.Login(ctx, email, password)
if err == nil {
r.refreshUserMessages()
}
return userData, err
}

func (r *LocalBackend) Logout(ctx context.Context, email string) (*account.UserData, error) {
return r.accountClient.Logout(ctx, email)
userData, err := r.accountClient.Logout(ctx, email)
if err == nil {
r.refreshUserMessages()
}
return userData, err
}

func (r *LocalBackend) FetchUserData(ctx context.Context) (*account.UserData, error) {
return r.accountClient.FetchUserData(ctx)
userData, err := r.accountClient.FetchUserData(ctx)
if err == nil {
r.refreshUserMessages()
}
return userData, err
}

func (r *LocalBackend) VerifyPassword(ctx context.Context, email, password string) error {
Expand All @@ -1604,7 +1666,11 @@ func (r *LocalBackend) CompleteRecoveryByEmail(ctx context.Context, email, newPa
}

func (r *LocalBackend) DeleteAccount(ctx context.Context, email, password string) (*account.UserData, error) {
return r.accountClient.DeleteAccount(ctx, email, password)
userData, err := r.accountClient.DeleteAccount(ctx, email, password)
if err == nil {
r.refreshUserMessages()
}
return userData, err
}

func (r *LocalBackend) SignUp(ctx context.Context, email, password string) ([]byte, *account.SignupResponse, error) {
Expand Down Expand Up @@ -1671,7 +1737,11 @@ func (r *LocalBackend) RemoveDevice(ctx context.Context, deviceID string) (*acco
}

func (r *LocalBackend) OAuthLoginCallback(ctx context.Context, oAuthToken string) (*account.UserData, error) {
return r.accountClient.OAuthLoginCallback(ctx, oAuthToken)
userData, err := r.accountClient.OAuthLoginCallback(ctx, oAuthToken)
if err == nil {
r.refreshUserMessages()
}
return userData, err
}

func (r *LocalBackend) OAuthDeviceLimitCallback(ctx context.Context, oAuthToken string) error {
Expand All @@ -1694,6 +1764,40 @@ func (r *LocalBackend) UserData() (*account.UserData, error) {
return &userData, nil
}

// CurrentUserMessage returns the current account's pending message.
func (r *LocalBackend) CurrentUserMessage() (*wire.ResolvedUserMessage, error) {
if r.userMessages == nil {
return nil, nil
}
return r.userMessages.Current()
}

// RefreshUserMessages schedules an immediate eligibility refresh.
func (r *LocalBackend) RefreshUserMessages() {
r.refreshUserMessages()
}

func (r *LocalBackend) refreshUserMessages() {
if r.userMessages != nil {
r.userMessages.Refresh()
}
}

// AcknowledgeUserMessage records that the UI displayed displayID.
func (r *LocalBackend) AcknowledgeUserMessage(displayID string) error {
if r.userMessages == nil {
return clientmessage.ErrMessageNotPending
}
return r.userMessages.Acknowledge(displayID)
}

// SetUserMessageActivity adjusts polling for app and network lifecycle changes.
func (r *LocalBackend) SetUserMessageActivity(active, online bool) {
if r.userMessages != nil {
r.userMessages.SetActivity(active, online)
}
}

///////////////////
// Subscriptions //
///////////////////
Expand Down
19 changes: 16 additions & 3 deletions cmd/lanternd/lanternd.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"time"

"github.com/alexflint/go-arg"
wire "github.com/getlantern/common/usermessage"

"github.com/getlantern/radiance/backend"
"github.com/getlantern/radiance/common"
Expand Down Expand Up @@ -396,9 +397,10 @@ func babysit(args []string, dataPath, logPath, logLevel string) error {
// daemonBackendOptions overrides only staging so the production default preserves an externally configured development environment.
func daemonBackendOptions(dataPath, logPath, logLevel string, environment daemonEnvironment) backend.Options {
options := backend.Options{
DataDir: dataPath,
LogDir: logPath,
LogLevel: logLevel,
DataDir: dataPath,
LogDir: logPath,
LogLevel: logLevel,
UserMessageCapabilities: userMessageCapabilities(),
}
if environment == daemonEnvironmentStaging {
options.EnvOverrides = map[string]string{
Expand All @@ -408,6 +410,17 @@ func daemonBackendOptions(dataPath, logPath, logLevel string, environment daemon
return options
}

func userMessageCapabilities() wire.ClientCapabilities {
return wire.ClientCapabilities{
Version: wire.CapabilityUserMessagesV1,
Surfaces: []wire.Surface{wire.SurfaceSnackbar},
Actions: []wire.ActionType{
wire.ActionTypeOpenHTTPSURL,
wire.ActionTypeOpenPlans,
},
}
}

func daemonBackendURLs(environment daemonEnvironment) (authURL, proServerURL string) {
if environment == daemonEnvironmentStaging {
return common.StageBaseURL, common.StageProServerURL
Expand Down
1 change: 1 addition & 0 deletions cmd/lanternd/lanternd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ func TestStagingServiceRunConfigSelectsStagingAccountEndpoints(t *testing.T) {
require.NoError(t, err)

options := daemonBackendOptions(parsed.dataPath, parsed.logPath, parsed.logLevel, parsed.environment)
require.Equal(t, userMessageCapabilities(), options.UserMessageCapabilities)
require.Equal(t, "staging", options.EnvOverrides[commonenv.ENV.String()])
t.Setenv(commonenv.ENV.String(), options.EnvOverrides[commonenv.ENV.String()])
require.Equal(t, common.StageBaseURL, common.GetBaseURL())
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ require (
github.com/alitto/pond v1.9.2
github.com/getlantern/amp v0.0.0-20260606002220-a8629924577c
github.com/getlantern/broflake v0.0.0-20260810172605-bef5e5234952
github.com/getlantern/common v1.2.1-0.20260708083946-cc657b08792c
github.com/getlantern/common v1.2.1-0.20260828200436-eb05c571820d
github.com/getlantern/dnstt v0.0.0-20260603191204-3b860502c0ac
github.com/getlantern/domainfront v0.0.0-20260722204513-8c1f8acfa715
github.com/getlantern/keepcurrent v0.0.0-20260616120552-f204338b01a3
Expand All @@ -67,6 +67,7 @@ require (
go.opentelemetry.io/otel/sdk v1.43.0
go.opentelemetry.io/otel/sdk/metric v1.43.0
golang.org/x/term v0.41.0
golang.org/x/text v0.35.0
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10
google.golang.org/protobuf v1.36.11
gopkg.in/natefinch/lumberjack.v2 v2.2.1
Expand Down Expand Up @@ -262,7 +263,6 @@ require (
golang.getoutline.org/sdk v0.0.21 // indirect
golang.getoutline.org/sdk/x v0.1.0 // indirect
golang.org/x/oauth2 v0.35.0 // indirect
golang.org/x/text v0.35.0 // indirect
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260504160031-60b97b32f348 // indirect
modernc.org/libc v1.22.3 // indirect
Expand Down
8 changes: 6 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -238,8 +238,12 @@ github.com/getlantern/amp v0.0.0-20260606002220-a8629924577c h1:ZzxuhIWO295y4eE6
github.com/getlantern/amp v0.0.0-20260606002220-a8629924577c/go.mod h1:b5teAOFT+vpBqc2CHoz74QTEM15iOv1PLdQogwXcfrM=
github.com/getlantern/broflake v0.0.0-20260810172605-bef5e5234952 h1:nD8iJ4IpaTq/09PhoOzmaGTwatOxsR41neSmbCY6RS8=
github.com/getlantern/broflake v0.0.0-20260810172605-bef5e5234952/go.mod h1:1+1kCIg9Zj+2CgN+vl868AlAZVUItuN4wLhFur5QakA=
github.com/getlantern/common v1.2.1-0.20260708083946-cc657b08792c h1:Hpxu12ORnAcyYuIqV2yAqrmDAnTaY1Cd3yL7TvFB6ME=
github.com/getlantern/common v1.2.1-0.20260708083946-cc657b08792c/go.mod h1:eSSuV4bMPgQJnczBw+KWWqWNo1itzmVxC++qUBPRTt0=
github.com/getlantern/common v1.2.1-0.20260818065623-10c2257aa54f h1:8iZqf4mUkFXKmUnDj2Iej0WySAzLuQmHOX736b4pCYU=
github.com/getlantern/common v1.2.1-0.20260818065623-10c2257aa54f/go.mod h1:eSSuV4bMPgQJnczBw+KWWqWNo1itzmVxC++qUBPRTt0=
github.com/getlantern/common v1.2.1-0.20260828192811-1233a8417569 h1:NmEbD0kKiL7EDjR0vSvkW+xtKwocgDHMk4SzvKQh/lE=
github.com/getlantern/common v1.2.1-0.20260828192811-1233a8417569/go.mod h1:eSSuV4bMPgQJnczBw+KWWqWNo1itzmVxC++qUBPRTt0=
github.com/getlantern/common v1.2.1-0.20260828200436-eb05c571820d h1:ere/uJH2664qGRepjvircloQN7WWCY4eQ+oiw2C4Hno=
github.com/getlantern/common v1.2.1-0.20260828200436-eb05c571820d/go.mod h1:9iL78jR5kZqr0E7oPxi38o6gPMhVa5dJBC5ngUgHzdg=
github.com/getlantern/context v0.0.0-20220418194847-3d5e7a086201 h1:oEZYEpZo28Wdx+5FZo4aU7JFXu0WG/4wJWese5reQSA=
github.com/getlantern/context v0.0.0-20220418194847-3d5e7a086201/go.mod h1:Y9WZUHEb+mpra02CbQ/QczLUe6f0Dezxaw5DCJlJQGo=
github.com/getlantern/dnstt v0.0.0-20260603191204-3b860502c0ac h1:TMvkNgLVyIYAfu1dYrOuRPYMVY+cHEo1C0CYWhtSw2A=
Expand Down
30 changes: 30 additions & 0 deletions ipc/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"syscall"
"time"

wire "github.com/getlantern/common/usermessage"
box "github.com/getlantern/lantern-box"

"github.com/getlantern/radiance/account"
Expand Down Expand Up @@ -227,6 +228,35 @@ func (c *Client) UpdateConfig(ctx context.Context) error {
return err
}

// CurrentUserMessage returns the pending message for the current account.
func (c *Client) CurrentUserMessage(ctx context.Context) (*wire.ResolvedUserMessage, error) {
var response CurrentUserMessageResponse
if err := c.doJSON(ctx, http.MethodGet, userMessageEndpoint, nil, &response); err != nil {
return nil, err
}
return response.Message, nil
}

// RefreshUserMessages schedules an immediate server eligibility refresh.
func (c *Client) RefreshUserMessages(ctx context.Context) error {
_, err := c.do(ctx, http.MethodPost, userMessageRefreshEndpoint, nil)
return err
}

// AcknowledgeUserMessage records that the UI displayed displayID.
func (c *Client) AcknowledgeUserMessage(ctx context.Context, displayID string) error {
_, err := c.do(ctx, http.MethodPost, userMessageAcknowledgeEndpoint,
UserMessageAcknowledgeRequest{DisplayID: displayID})
return err
}

// SetUserMessageActivity updates the app and connectivity lifecycle used by polling.
func (c *Client) SetUserMessageActivity(ctx context.Context, active, online bool) error {
_, err := c.do(ctx, http.MethodPatch, userMessageActivityEndpoint,
UserMessageActivityRequest{Active: active, Online: online})
return err
}

///////////////////////
// Server management //
///////////////////////
Expand Down
Loading
Loading