Skip to content

feat: expose getServerLocations FFI for pro user location picker#8577

Closed
myleshorton wants to merge 13 commits intomainfrom
feat/server-locations-ffi
Closed

feat: expose getServerLocations FFI for pro user location picker#8577
myleshorton wants to merge 13 commits intomainfrom
feat/server-locations-ffi

Conversation

@myleshorton
Copy link
Contributor

Summary

  • Add GetServerLocations() to LanternCore — returns all available locations from ConfigResponse.Servers
  • Add getServerLocations FFI export (wrapped with runOnGoStack)
  • This returns all locations the user can select, not just ones with active routes

Context

For pro user location selection with the bandit system, the Flutter UI needs the full location list (not just active outbounds). When a user selects a location:

  1. Flutter calls SetPreferredServer(country, city)
  2. Radiance refetches config with the preferred location
  3. Server's bandit includes that region in selection
  4. New config arrives with routes for that location

Test plan

  • Verify getServerLocations returns the config response's server list
  • Verify the list includes locations the user doesn't currently have routes for

🤖 Generated with Claude Code

myleshorton and others added 4 commits March 25, 2026 07:48
…panics

The radiance API methods are wrapped with RunOffCgoStack, but the FFI
bridge in lantern allocates Go memory (base64 encoding, C.CString)
AFTER the radiance call returns — still on the CGo callback stack.
This triggers bulkBarrierPreWrite panics on macOS.

Add runOnGoStack() helper that runs the entire FFI function body on a
real Go goroutine. Applied to: getUserData, fetchUserData, login,
signup, logout, oAuthLoginCallback, deleteAccount.

Fixes getlantern/engineering#3102

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace local runOnGoStack with a thin wrapper around
common.RunOffCgoStack from radiance, avoiding code duplication.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add GetServerLocations() which returns all available server locations
from the config response (ConfigResponse.Servers). Unlike
GetAvailableServers() which returns active outbounds from the sing-box
config, this returns every location the user can select — including
ones without current routes.

The Flutter location picker should use this instead of
getAvailableServers so pro users see all available locations. Selecting
a location triggers SetPreferredServer which refetches config with
routes for that location.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 25, 2026 17:12
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR exposes a new API to retrieve the full list of selectable VPN server locations (not just currently-active routes) by adding a LanternCore method and exporting it over the Go FFI for the Flutter “pro user location picker” flow.

Changes:

  • Add GetServerLocations() to LanternCore (marshals Radiance server locations to JSON).
  • Export getServerLocations over FFI, and introduce a runOnGoStack helper to execute FFI logic off the cgo callback stack.
  • Refactor several existing FFI exports to use runOnGoStack to avoid GC/write-barrier panics.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.

File Description
lantern-core/ffi/ffi.go Adds runOnGoStack, exports getServerLocations, and wraps several FFI exports to run off the cgo callback stack.
lantern-core/core.go Extends the core interface and implements GetServerLocations() to return all available server locations as JSON.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

myleshorton and others added 9 commits March 25, 2026 11:28
Wire up the getServerLocations FFI export through all layers:
- lantern_generated_bindings.dart: FFI binding
- lantern_core_service.dart: interface method
- lantern_ffi_service.dart: desktop implementation
- lantern_platform_service.dart: mobile implementation (method channel)
- lantern_service.dart: router (FFI vs platform)

Returns all available server locations from the config response,
enabling the location picker to show locations the user can select
even if they don't currently have routes for them.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Full client-side wiring for pro user server location selection:

Go layer:
- SetPreferredServerLocation in core.go + FFI export
- getServerLocations returns all available locations from config

Flutter layer:
- serverLocationsProvider (Riverpod) fetches all locations
- Pro users see all locations (not just active outbounds)
- Selecting a location calls setPreferredServerLocation, which
  triggers a config refetch with that region included in the
  bandit selection
- Free users continue using the existing outbound-based list

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Move all C.GoString conversions inside runOnGoStack closures so
  Go string allocations happen on a real goroutine, not the cgo stack
- Handle error from RunOffCgoStack (panic recovery) via SendError
  instead of silently discarding it

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pro users see "Smart Protocol" as the subtitle for each location
since the bandit system selects the optimal protocol. Free users
continue seeing the specific protocol name from the outbound tag.

- Add showSmartProtocol flag to SingleCityServerView and
  _CountryCityListView
- Add smart_protocol i18n string
- Protocol label logic: show specific protocol if set, otherwise
  "Smart Protocol" for pro users, or nothing for free users

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add Go mobile bindings, Android Kotlin handler, and iOS Swift handler
for both methods. Fixes "No implementation found for method
getServerLocations on channel" error on mobile platforms.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
macOS uses gomobile method channels (same as iOS), not direct FFI.
Add both methods to the macOS MethodHandler.swift.

Platform coverage:
- Windows/Linux: FFI exports in ffi.go (already there)
- macOS: method channel handler (this commit)
- Android: method channel handler (previous commit)
- iOS: method channel handler (previous commit)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix null crash when getServerLocations returns empty/null
- All users see all locations (free users see them but can't select)
- Fall back to outbound-based locations if server locations unavailable

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Per Jigar's feedback: move the merge logic from Flutter into radiance.
The getServerLocations FFI now calls AllLocations() which returns a
unified list with tag, protocol, and active status per location.

- radiance: AllLocations() merges ConfigResponse.Servers + active outbounds
- core.go: GetAllLocations() wraps the radiance method
- FFI/mobile: getServerLocations calls GetAllLocations with fallback
- Flutter: provider simplified, just parses the unified response
- Bump radiance to include AllLocations()

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants