Skip to main content

Server Locations

Fetching Locations​

Fetch the list of available VPN servers after authentication:

import KapeVPN

let locations = KapeLocationService(handle: handle)
try await locations.fetchLocations()
info

KapeLocationService ships in the KapeVPNDiscovery module, which depends on KapeCore alone. KapeVPN re-exports it, so the import above is unchanged. If your target needs locations but not the tunnel, depend on KapeVPNDiscovery directly and construct the service with KapeLocationService(handle:selectedProtocolProvider:) — see OpenVPN Endpoint Configuration.

Locations are available as a flat list or grouped by continent:

// All locations
locations.allLocations // [KapeServerLocation]

// Grouped by continent
locations.locationsByContinent // [String: [KapeServerLocation]]
// e.g. ["Europe": [...], "North America": [...]]

Each KapeServerLocation provides:

PropertyTypeDescription
idStringLocation ID (pass to connect(to:))
nameStringHuman-readable name (e.g. "Frankfurt")
countryStringISO 3166-1 alpha-2 country code
continentStringContinent or region name
latitudeDoubleLatitude coordinate
longitudeDoubleLongitude coordinate
ratingKapeLocationRating?Quality rating (smart locations only)
pingMsUInt32?Latest measured ping, in milliseconds (set by pingLocations/refreshSmartLocationsThenPing)

KapeLocationRating values: excellent, good, ok, degraded, poor, unknown. The enum is Comparable — higher ratings sort greater.

fetchLocations() uses the protocol currently stored in shared state. To ask for a specific protocol — "which locations serve OpenVPN?" — use KapeVPNDiscoveryService.fetchLocations(for:), which takes the protocol as an argument and reads no shared state. See OpenVPN Endpoint Configuration.

Smart Locations​

Smart locations are the SDK's recommended servers for the current user. The rating combines several signals: ping latency (min, avg, median, p90), short HTTP download and upload measurements, and geographic distance. Results are cached per network — when the user reconnects to a known WiFi network, cached results are returned immediately without re-running the full measurement.

KapeLocationService exposes three methods that cover this — pick the one that matches what you need

MethodWhat it doesProgress
fetchSmartLocations(forceRefresh:extendedTest:)Full measurement (ping +, unless disabled, HTTP tests) and ranking.none
pingLocations(onProgress:)Pure ping sweep — no ranking, no speed test.per location
refreshSmartLocationsThenPing(...)fetchSmartLocations, then a full ping sweep that refreshes pingMs on the ranked result.both phases

Identical requests (e.g. two pingLocations()) made concurrently against the same KapeLocationService instance are coordinated automatically, only one test will run and the result will be share between the requests. If a second request is submitted after the first one has ended, the test is started from beginning to fetch new results. Different requests (e.g. one fetchSmartLocations() and one pingLocations()) made concurrently are serialized: only after the first test is done that the second test will start.

Full measurement + ranking​

Fetch smart locations after fetchLocations():

try await locations.fetchSmartLocations()
locations.smartLocations // [KapeServerLocation] — sorted by best rating

To disable HTTP tests (recommended on cellular to save data and reduce measurement time):

try await locations.fetchSmartLocations(extendedTest: false)

Ping only​

For a pure latency sweep with no ranking or speed test, with per-location progress as each one completes:

try await locations.pingLocations { progress in
Task { @MainActor in
print("\(progress.locationId): \(progress.pingMs ?? 0) ms")
}
}

Ranking, then fresh ping progress​

To get the ranked list as soon as it's available (fast when cached), then watch per-location progress as latency is refreshed against the full location list:

try await locations.refreshSmartLocationsThenPing(
onSmartLocationsUpdate: { ranked in
Task { @MainActor in print("ranked: \(ranked.count) locations") }
},
onPingProgress: { progress in
Task { @MainActor in print("\(progress.locationId): \(progress.pingMs ?? 0) ms") }
}
)
locations.smartLocations // final result: ranking with pingMs refreshed
info

Call fetchLocations() before any of the three methods above — smart/ping locations are resolved by matching against the full location list.

Network Monitoring​

KapeNetworkHelper (part of KapeCore) keeps the SDK aware of the current network and exposes the WiFi identity for display in your UI. It also enables per-network SmartLocation caching — when the user reconnects to a known network, cached results are returned immediately.

Setup​

Create one instance and call start() once after the SDK handle is ready — typically in your app's root object or view model:

import KapeCore

let networkHelper = KapeNetworkHelper(handle: handle)
networkHelper.start()

KapeNetworkHelper uses NWPathMonitor to detect network changes. On each change it reads the current SSID and BSSID, pushes them into the SDK configuration, and calls networkChanged() so the SDK can invalidate or restore cached SmartLocation results for that network.

Call stop() when the helper is no longer needed:

networkHelper.stop()

Observing network info in SwiftUI​

KapeNetworkHelper is an ObservableObject with two published properties:

PropertyTypeDescription
ssidString?WiFi network name. nil on cellular or when entitlement is missing.
bssidString?WiFi access point MAC address. nil on cellular or when entitlement is missing.
// In your view model
@Published private(set) var wifiSSID: String?

networkHelper.$ssid.assign(to: &$wifiSSID)
// In your SwiftUI view
Section("Network") {
LabeledContent("SSID") { Text(viewModel.wifiSSID ?? "–") }
}

iOS entitlement​

On iOS, reading the SSID and BSSID requires the com.apple.developer.networking.wifi-info entitlement. Add it to your app target's .entitlements file:

<key>com.apple.developer.networking.wifi-info</key>
<true/>

Without this entitlement, ssid and bssid are always nil. The helper still functions — network-change signals are sent to the SDK — but per-network caching has no benefit since all networks appear identical.

info

On macOS, no entitlement is required. WiFi info is read via CoreWLAN.

info

On tvOS, ssid and bssid are always nil. Network-change signals are still sent to the SDK.

warning

Do not instantiate KapeNetworkHelper in the PacketTunnel extension. The extension does not need SmartLocation caching and has a strict memory budget.