Skip to content
Open
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
6 changes: 6 additions & 0 deletions assets/locales/en.po
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,12 @@ msgstr "Block Ads"
msgid "only_active"
msgstr "Only active when VPN is connected"

msgid "share_my_connection"
msgstr "Share My Connection"

msgid "share_my_connection_subtitle"
msgstr "Let other Lantern users route through your connection to bypass censorship."

msgid "vpn_connected"
msgstr "Lantern is now connected."

Expand Down
9 changes: 7 additions & 2 deletions lib/core/models/radiance_settings_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,29 @@ class RadianceSettingsState {
final RoutingMode routingMode;
final bool splitTunneling;
final bool telemetry;
final bool peerProxy;

const RadianceSettingsState({
this.blockAds = false,
this.routingMode = RoutingMode.full,
this.splitTunneling = false,
this.telemetry = false,
this.peerProxy = false,
});

RadianceSettingsState copyWith({
bool? blockAds,
RoutingMode? routingMode,
bool? splitTunneling,
bool? telemetry,
bool? peerProxy,
}) {
return RadianceSettingsState(
blockAds: blockAds ?? this.blockAds,
routingMode: routingMode ?? this.routingMode,
splitTunneling: splitTunneling ?? this.splitTunneling,
telemetry: telemetry ?? this.telemetry,
peerProxy: peerProxy ?? this.peerProxy,
);
}

Expand All @@ -41,9 +45,10 @@ class RadianceSettingsState {
blockAds == other.blockAds &&
routingMode == other.routingMode &&
splitTunneling == other.splitTunneling &&
telemetry == other.telemetry;
telemetry == other.telemetry &&
peerProxy == other.peerProxy;

@override
int get hashCode =>
Object.hash(blockAds, routingMode, splitTunneling, telemetry);
Object.hash(blockAds, routingMode, splitTunneling, telemetry, peerProxy);
}
20 changes: 20 additions & 0 deletions lib/features/home/provider/radiance_settings_providers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,23 @@ class RadianceSettings extends _$RadianceSettings {
final routingF = svc.isSmartRoutingEnabled();
final telemetryF = svc.isTelemetryEnabled();
final splitF = PlatformUtils.isIOS ? null : svc.isSplitTunnelingEnabled();
// Peer-proxy probe runs only on platforms with native handlers
// (Windows + Linux via FFI, macOS via MethodChannel — i.e. all desktop).
// On iOS / Android the call would fail with MissingPluginException on
// every settings init.
final peerF = PlatformUtils.isDesktop ? svc.isPeerProxyEnabled() : null;

final results = await Future.wait([
blockAdsF,
routingF,
telemetryF,
?splitF,
?peerF,
]);
if (!ref.mounted) return;

const defaults = RadianceSettingsState();
final peerIdx = 3 + (splitF == null ? 0 : 1);
state = RadianceSettingsState(
blockAds: results[0].fold((_) => defaults.blockAds, (v) => v),
routingMode: results[1].fold(
Expand All @@ -49,6 +56,9 @@ class RadianceSettings extends _$RadianceSettings {
splitTunneling: splitF == null
? defaults.splitTunneling
: results[3].fold((_) => defaults.splitTunneling, (v) => v),
peerProxy: peerF == null
? defaults.peerProxy
: results[peerIdx].fold((_) => defaults.peerProxy, (v) => v),
);
}

Expand Down Expand Up @@ -97,6 +107,16 @@ class RadianceSettings extends _$RadianceSettings {
(_) => state = state.copyWith(telemetry: consent),
);
}

Future<void> setPeerProxy(bool value) async {
final svc = ref.read(lanternServiceProvider);
final result = await svc.setPeerProxyEnabled(value);
if (!ref.mounted) return;
result.fold(
(err) => appLogger.error('setPeerProxyEnabled failed: ${err.error}'),
(_) => state = state.copyWith(peerProxy: value),
);
}
}

/// Fetches whether user logged in via OAuth from radiance.
Expand Down
33 changes: 33 additions & 0 deletions lib/features/setting/vpn_setting.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ class VPNSetting extends HookConsumerWidget {
final telemetryConsent = ref.watch(
radianceSettingsProvider.select((s) => s.telemetry),
);
final peerProxy = ref.watch(
radianceSettingsProvider.select((s) => s.peerProxy),
);

return ListView(
padding: const EdgeInsets.all(0),
Expand Down Expand Up @@ -117,6 +120,36 @@ class VPNSetting extends HookConsumerWidget {
},
),
),
if (PlatformUtils.isDesktop) ...{
SizedBox(height: 16),
AppCard(
padding: EdgeInsets.zero,
child: AppTile(
label: 'share_my_connection'.i18n,
subtitle: Text(
'share_my_connection_subtitle'.i18n,
style: textTheme.labelMedium!.copyWith(
color: context.textTertiary,
letterSpacing: 0.0,
),
),
icon: AppImagePaths.share,
trailing: SwitchButton(
value: peerProxy,
onChanged: (bool? value) {
ref
.read(radianceSettingsProvider.notifier)
.setPeerProxy(value ?? false);
},
),
onPressed: () {
ref
.read(radianceSettingsProvider.notifier)
.setPeerProxy(!peerProxy);
},
),
),
},
SizedBox(height: 16),
AppCard(
padding: EdgeInsets.zero,
Expand Down
4 changes: 4 additions & 0 deletions lib/lantern/lantern_core_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ abstract class LanternCoreService {

Future<Either<Failure, bool>> isBlockAdsEnabled();

Future<Either<Failure, Unit>> setPeerProxyEnabled(bool enabled);

Future<Either<Failure, bool>> isPeerProxyEnabled();

Future<Either<Failure, bool>> isSmartRoutingEnabled();

Future<Either<Failure, bool>> isTelemetryEnabled();
Expand Down
28 changes: 28 additions & 0 deletions lib/lantern/lantern_ffi_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1508,6 +1508,34 @@ class LanternFFIService implements LanternCoreService {
}
}

@override
Future<Either<Failure, Unit>> setPeerProxyEnabled(bool enabled) async {
try {
final result = await runInBackground<String>(() async {
return _ffiService
.setPeerProxyEnabled(enabled ? 1 : 0)
.cast<Utf8>()
.toDartString();
});
checkAPIError(result);
return right(unit);
} catch (e, st) {
appLogger.error('setPeerProxyEnabled error: $e', e, st);
return Left(e.toFailure());
}
}

@override
Future<Either<Failure, bool>> isPeerProxyEnabled() async {
try {
final res = _ffiService.isPeerProxyEnabled();
return right(res != 0);
} catch (e, st) {
appLogger.error('isPeerProxyEnabled error: $e', e, st);
return Left(e.toFailure());
}
}

@override
Future<Either<Failure, bool>> isSmartRoutingEnabled() async {
try {
Expand Down
20 changes: 20 additions & 0 deletions lib/lantern/lantern_generated_bindings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6253,6 +6253,26 @@ class LanternBindings {
late final _isBlockAdsEnabled = _isBlockAdsEnabledPtr
.asFunction<int Function()>();

ffi.Pointer<ffi.Char> setPeerProxyEnabled(int enabled) {
return _setPeerProxyEnabled(enabled);
}

late final _setPeerProxyEnabledPtr =
_lookup<ffi.NativeFunction<ffi.Pointer<ffi.Char> Function(ffi.Int)>>(
'setPeerProxyEnabled',
);
late final _setPeerProxyEnabled = _setPeerProxyEnabledPtr
.asFunction<ffi.Pointer<ffi.Char> Function(int)>();

int isPeerProxyEnabled() {
return _isPeerProxyEnabled();
}

late final _isPeerProxyEnabledPtr =
_lookup<ffi.NativeFunction<ffi.Int Function()>>('isPeerProxyEnabled');
late final _isPeerProxyEnabled = _isPeerProxyEnabledPtr
.asFunction<int Function()>();

ffi.Pointer<ffi.Char> setSmartRoutingEnabled(int enabled) {
return _setSmartRoutingEnabled(enabled);
}
Expand Down
24 changes: 24 additions & 0 deletions lib/lantern/lantern_platform_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,30 @@ class LanternPlatformService implements LanternCoreService {
}
}

@override
Future<Either<Failure, Unit>> setPeerProxyEnabled(bool enabled) async {
try {
await _methodChannel.invokeMethod('setPeerProxyEnabled', {
'enabled': enabled,
});
return right(unit);
} catch (e, st) {
appLogger.error('setPeerProxyEnabled failed', e, st);
return Left(e.toFailure());
}
}

@override
Future<Either<Failure, bool>> isPeerProxyEnabled() async {
try {
final res = await _methodChannel.invokeMethod<bool>('isPeerProxyEnabled');
return right(res ?? false);
} catch (e, st) {
appLogger.error('isPeerProxyEnabled failed', e, st);
return Left(e.toFailure());
}
}

@override
Future<Either<Failure, bool>> isSmartRoutingEnabled() async {
try {
Expand Down
16 changes: 16 additions & 0 deletions lib/lantern/lantern_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -773,6 +773,22 @@ class LanternService implements LanternCoreService {
return _platformService.setBlockAdsEnabled(enabled);
}

@override
Future<Either<Failure, bool>> isPeerProxyEnabled() {
if (PlatformUtils.isFFISupported) {
return _ffiService.isPeerProxyEnabled();
}
return _platformService.isPeerProxyEnabled();
}

@override
Future<Either<Failure, Unit>> setPeerProxyEnabled(bool enabled) {
if (PlatformUtils.isFFISupported) {
return _ffiService.setPeerProxyEnabled(enabled);
}
return _platformService.setPeerProxyEnabled(enabled);
}

@override
Future<Either<Failure, bool>> isSmartRoutingEnabled() {
if (PlatformUtils.isFFISupported) {
Expand Down
24 changes: 24 additions & 0 deletions macos/Runner/Handlers/MethodHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,16 @@ class MethodHandler {
let enabled = data?["enabled"] as? Bool ?? false
self.setBlockAdsEnabled(result: result, enabled: enabled)

case "isPeerProxyEnabled":
Task {
await MainActor.run { result(MobileIsPeerShareEnabled()) }
}

case "setPeerProxyEnabled":
let data = call.arguments as? [String: Any]
let enabled = data?["enabled"] as? Bool ?? false
self.setPeerProxyEnabled(result: result, enabled: enabled)

case "updateTelemetryEvents":
guard let consent: Bool = self.decodeValue(from: call.arguments, result: result) else {
return
Expand Down Expand Up @@ -1131,6 +1141,20 @@ class MethodHandler {
}
}

func setPeerProxyEnabled(result: @escaping FlutterResult, enabled: Bool) {
Task {
var error: NSError?
MobileSetPeerShareEnabled(enabled, &error)
if let error {
await self.handleFlutterError(error, result: result, code: "SET_PEER_PROXY_ERROR")
return
}
await MainActor.run {
result("ok")
}
}
}

func updateTelemetryEvents(consent: Bool, result: @escaping FlutterResult) {
Task {
var error: NSError?
Expand Down