Skip to content

iOS production-readiness: security, localization (RU/FA), parity, polish#1

Open
pavel-shpilev wants to merge 66 commits into
masterfrom
mrmidi-base
Open

iOS production-readiness: security, localization (RU/FA), parity, polish#1
pavel-shpilev wants to merge 66 commits into
masterfrom
mrmidi-base

Conversation

@pavel-shpilev

Copy link
Copy Markdown

iOS production-readiness — requesting your review

This branch takes the iOS client (plus the macOS/tvOS targets) through a full production-readiness pass on the current protocol. It builds clean (iOS-sim + macOS) and is device-verified on iPhone 14 Pro / iOS 26.5 — login persistence, data plane, reconnect churn, memory (footprint well under the NE ceiling), clean disconnects, and auto-connect all confirmed on device.

What's in it

  • Release blockers + security/crash: removed a cleartext-password log line and stopped persisting the password to UserDefaults/iCloud KVS (it now lives only in the synchronizable iCloud Keychain); added PrivacyInfo.xcprivacy; trimmed the app's NetworkExtension entitlement to packet-tunnel-provider only; fixed a shared_ptr data race in the WebSocket bridge; the keychain team prefix is derived at runtime; the bundle-id namespace is env-driven; the native build honors the Release configuration for archives.
  • Correctness: connect re-entrancy guard; a real timeout on the provider IPC; the reconnect budget now actually terminates the tunnel when exhausted (0 still = infinite); auto-select verifies candidates and picks a working server instead of an unverified runner-up; JSON-encoded login body; deleted lifecycle state machines that were never driven in production (false-confidence tests).
  • Localization: dynamic strings now localize at runtime (previously ru never displayed); raw errors map to friendly localized messages; added Persian (fa) with locale-aware speed formatting and CLDR plurals.
  • Parity: bundled the SNI dictionaries as the scanner default; added On-Demand VPN; removed the per-app VPN UI/plumbing (it can't function on consumer iOS without a managed/MDM entitlement).
  • Polish: VoiceOver labels on the Connect control + tabs, server-list empty/loading states, a "Disconnecting…" state, NavigationStack, and an About screen; the shipped .app no longer carries the C++ framework headers.
  • macOS/tvOS: ported the KVS-password and reconnect-budget fixes.
  • Cleanups: removed a dead IP-assignment code path, deduplicated the C++ strategy parser, and throttled the diagnostics I/O.

⚠️ libfptn submodule — one follow-up after #291

FptnLib/fptn currently points at a personal fork (pavel-shpilev/fptn @ e0422a1) so this branch resolves on a fresh clone today. That commit is exactly upstream + the 3 Apple build/keepalive commits that are PR #291 (fptn-project/fptn#291).

Once #291 merges into fptn-project/fptn master, I'll repoint the submodule back to the canonical repo and bump the pointer to the merged commit — a one-line follow-up commit, no rebase. So please review #291 alongside this; the fork is only a stopgap so reviewers can build the iOS side now.

Notes / follow-ups (not blocking)

  • Persian (fa) should get a native-speaker review before release — it's a careful first pass with technical terms kept in Latin, but plural grammar and longer strings deserve a check.
  • russia.sni (~59k entries) ships in the app bundle; consider curating to a top-N or capping the scan count.
  • macOS/tvOS keychain stores still hardcode the team prefix (iOS derives it at runtime) — small parity follow-up.
  • Export compliance: the blanket ITSAppUsesNonExemptEncryption=false was removed, so the questionnaire gets answered at App Store Connect upload.

🤖 Generated with Claude Code

mrmidi and others added 30 commits February 23, 2026 09:36
Phase 0 — Swift 6 Concurrency Compliance
- Models (VPNConnection, VPNServer, FPTNToken): explicit Sendable conformance
- TokenService, ServerSelectionService: converted to actor
- VPNService: @mainactor + full async/await pipeline; fixed 3 bugs:
  * crash on empty server list (guard let servers.first)
  * dead code in configureAndStartVPN
  * missing completion handler call after saveToPreferences
- NETunnelProviderManager data race: NEManagerBox (@unchecked Sendable)
  wrapper + MainActor.assumeIsolated (NE callbacks are always main-thread)
- Timer closures: wrapped in Task { @mainactor } to access isolated state
- SWIFT_STRICT_CONCURRENCY = complete on all 4 build configs
- FptnVPNTunnel Swift version bumped 5.0 → 6.0
- @preconcurrency import NetworkExtension

Phase 0.5 — Structured Logging (swift-log)
- Added swift-log (Logging) to FptnVPN + FptnVPNTunnel targets
- FptnVPN/Logging/FptnLogger.swift: AppLogHandler (os_log + file sink)
- FptnVPN/Logging/SharedLogSink.swift: rolling App Group log file
- FptnVPN/Logging/CredentialRedactor.swift: hideCred() utility
- FptnVPNTunnel/Logging/TunnelLogger.swift: mirror handler for tunnel process
- FptnVPNTunnel/Logging/SharedLogSink.swift: tunnel-side file sink copy
- FptnVPNApp: bootstrapLogging() in init()
- All print() replaced with logger.* + hideCred() on credentials
- DebugLogView (#if DEBUG): tails shared log file; accessible via long-press
- App Group entitlement added to FptnVPN.entitlements (was missing)

Phase 1 — MVVM Architecture
- Added ViewModels/HomeViewModel, LoginViewModel, ServerListViewModel
- HomeView, LoginView, ServerListView: pure rendering surfaces
- TokenValidator class removed; logic absorbed into LoginViewModel
- Server selection callbacks wired: ServerListView → HomeViewModel → VPNService

Build settings / warnings
- Removed bogus LD_RUNPATH_SEARCH_PATHS[arch=*] = -lfptn-protocol-lib_static
- Fixed framework search path (removed path ending in .framework)
- Added UIRequiresFullScreen = YES (portrait-only app)
- Prefixed dead _packetBlock/_connectedBlock in WebScoketClientBridge
Token parsing (LoginViewModel):
- Support new fptnb: format (base64 of Brotli-compressed JSON, bot commit d1ed549)
  using Apple's built-in Compression framework — no new dependencies
- Fix re-padding corruption when token arrives with existing = signs
- Strip backticks that Telegram includes when user copies the full message
- Normalize URL-safe base64 chars (- → +, _ → /)
- Strip fptnb: prefix before fptn: to prevent partial match corruption

VPN service (VPNService):
- Replace NEAppProxyProvider stub with NEPacketTunnelProvider that owns the
  WebSocket and TUN interface inside the extension process
- Fix bundle ID mismatch: org.fptn.* → net.mrmidi.* so NE can locate the
  extension (was causing NEAgentErrorDomain Code=2 / PID 0 crash)
- Call startVPNTunnel() after saveToPreferences — the missing call that
  actually launches the extension and routes device traffic
- Reuse existing NETunnelProviderManager instead of creating duplicates
- Drive isConnected from NEVPNStatusDidChange observer, not WebSocket start
- Remove duplicate app-process WebSocket (extension owns traffic now)

Project (project.pbxproj):
- Embed fptn_native_lib.framework in FptnVPNTunnel target so dyld can load
  it when the extension process launches

Tooling:
- Add token_debug.py: decodes and validates both fptn: and fptnb: tokens,
  shows server list, md5 fingerprints, and iOS compatibility warnings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- add dSYM generation to CMakeLists
- create Info.plist.in to embed in framework
- update build script to extract dSYM and add MinimumOSVersion
- update C++ wrapper bridge to accept censorship_strategy
- update Swift WebScoketClientBridge classes to send 'SNI' default strategy
- add KeychainHelper to securely persist tokens
- integrate AppFilterService and SettingsService
- build new SettingsView with proxy routing rules UI
- update Home/Login Views to match new models
- track token_debug.py properly
- ignore generated fptn.icon folder
- proper conan targets for iOS framework linking
- use ntp client master branch zip
- Add .preferredColorScheme(.dark) to root view to fix white text scaling in iOS Light Mode
- Make loginToServer and getDNSInfo nonisolated to prevent synchronous C++ networking calls from deadlocking the UI MainActor
…tics

- add app-wide semantic adaptive color tokens and keep Color.cian compatibility alias

- add persisted appearance preference (system/dark/light) via SettingsService, SettingsViewModel, and SettingsView

- update FptnVPNApp to apply preferredColorScheme from stored settings

- refactor SettingsView and AppFilterView to remove hardcoded dark-only colors and forced dark toolbar behavior

- refactor HomeView and LoginView to use adaptive surfaces/text colors for light-mode readability

- add VPN connection state fields (isConnecting, errorMessage) to VPNConnection and expose through HomeViewModel

- update VPNService connect flow with user-facing failure messages and manager return-based startup handling

- add MetricKitManager service and initialize it on app startup for crash/hang diagnostics logging

- remove debug view forced dark scheme to align with selected appearance

- add root CHANGELOG.md documenting this release
Introduce a new Tools entry in HomeView and add an SNI Checker workflow for ad-hoc probing of candidate SNI hostnames.

Included components:

- SNICheckerView UI for server/method selection, SNI list input, progress, filtering, and apply actions.

- SNIScannerViewModel state handling for scan lifecycle, buffering, best-result tracking, and SNI apply integration.

- ScannerEngine/Prober service layer for sanitization, deduplication, domain validation, concurrent probing, and streamed scan events.

- Shared probe models (ProbeConfig, ProbeResult, ProbeStatus, progress/event types).

- ToolsView container and HomeView navigation entry to present the checker as a sheet.

Also update CHANGELOG.md to document the feature and explicitly mark it as highly experimental and not expected to work reliably in all environments yet (exploratory use).
Implement a production-facing logging workflow tailored to FPTN with a dedicated Home -> Logs entry, Warning as the default level, and app/tunnel synchronization.

Included:

- New LogLevel model (warning/info/debug) and persisted setting in SettingsService with default Warning.

- Settings UI + view model support for selecting log level.

- App logger (FptnLogger) reads effective level from settings.

- Tunnel logger runtime level store and extension IPC command handling for set_log_level/ping.

- VPNService integration to pass logLevel in providerConfiguration and push live updates via sendProviderMessage.

- New LogsView + LogsViewModel with severity/source filters, follow/pause, clear, and copy-to-clipboard export.

- HomeView bottom navigation now includes a Logs button and sheet.

Investigation-driven fixes for observed user confusion:

- Added default "Recent only (24h)" filtering so historical errors from previous days do not appear as active failures by default.

- Added warning-level tunnel lifecycle markers (start/websocket connected/settings applied) so tunnel activity remains visible at default Warning level.

Also updated CHANGELOG.md with feature and fixes.
Add initial macOS host and Network Extension placeholder targets to the Xcode project, including app/extension plist, entitlements, assets, and starter test targets.

Also improve iOS home-state correctness by syncing VPN status from system preferences when Home appears and when the app becomes active (Control Center/Settings initiated VPN changes are now reflected in UI).

Included functional changes:

- VPNService.syncWithSystem() to load existing NETunnelProviderManager and refresh observed status.

- HomeViewModel.syncWithSystem() passthrough.

- HomeView scene-phase hooks to trigger sync on appear and on active phase.
- Add GitHub Actions workflow to run xcodebuild tests on macOS-26

- Add zsh bootstrap script to generate .env.local and optionally upload secrets

- Fix tunnel websocket bridge signature mismatch
mrmidi and others added 30 commits April 28, 2026 16:22
- Extend BypassMethod→CensorshipStrategy with 8 browser-specific Reality profiles
  (Chrome 145/146/147, Firefox 149, Yandex 24/25/26, Safari 26)
- Add simpleCases/advancedCases groups, displayName, helpText, isAdvanced
- SettingsService: add censorshipStrategy persistence, keep bypassMethod compat
- SettingsView: main picker shows simple modes, DisclosureGroup for advanced profiles
- SNIProbe: add handshakeLatencyMs, httpCode, strategyRawValue to results
- SNIScanner: replace HttpsClient with ApiClientBridge handshake probing;
  use AsyncStream + TaskGroup for concurrent scanning with cancellation
- ServerSelectionService: verify best TCP server via handshake+DNS before auto-select
- SNICheckerView: add profile picker for advanced Reality strategies,
  result detail sheet showing handshake latency and HTTP status
- Reduce kDefaultIdleTimeoutSeconds 300→60 to match server session timeout
- Reduce TunnelConfiguration fallback 300→60
- WebSocket bridge: add status reporting (running, started, idle_timeout,
  last_error, last_disconnect_reason) via websocket_client_bridge_status()
- Add WebsocketClientStatus struct (Sendable) in Swift wrappers
- Extend TunnelRuntimeSnapshot with DNS addresses, tunnel IPs, IPv6 state,
  and WebSocket health fields for end-to-end diagnostics
- Add wake() health check: if connected but WebSocket isn't running after
  device wake, triggers transport disconnected → reconnection flow
- Clear shared log file on app start
- Bump submodule pointer to f964b7b (TCP keepalive + wired idle_timeout)
- Add CFBundleDevelopmentRegion en + CFBundleLocalizations [en, ru] to all app targets
- Create iOS Localizable.xcstrings with 95 English→Russian translations
- Create macOS Localizable.xcstrings with 13 translations
- Create tvOS Localizable.xcstrings with 27 translations
- Covers all user-facing UI: login, home, settings, scanner, logs, per-app VPN
- String Catalogs (.xcstrings) auto-resolve with SwiftUI Text() — no code changes needed
- Language auto-selected from system locale; English is the fallback
- Add Fptn-macOSTests target to project.yml so the Fptn-macOS scheme is
  configured for the test action (was failing with exit code 66)
- Add FRAMEWORK_SEARCH_PATHS to both FptnVPNTests and Fptn-macOSTests so
  the bridging header can be resolved during dependency scanning (Xcode 26)
- Mark CloudTokenSync @mainactor — NSUbiquitousKeyValueStore is main-thread-only;
  nonisolated on the string constants was redundant and removed
- Upgrade actions/cache v4 → v5 and add save-always: true so Conan cache
  is preserved even when a subsequent step fails

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace deprecated save-always with explicit cache/restore + cache/save
  steps (if: always()) so Conan cache is saved even when tests fail
- Fix @mainactor string keys referenced from @sendable closure in
  startObserving by capturing as a local Set<String> before the closure

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add restore/save steps for .build/DerivedData keyed on Swift sources,
project.yml and Package.resolved. restore-keys fallback means partial
hits (code changes) still warm the incremental compiler instead of
rebuilding cold. Saves ~6m per run once the cache is populated.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implement Swift-side tunnel lifecycle state handling with explicit stop intent, generation-checked websocket callbacks, path-aware reconnect scheduling, and app prepare_stop flows for iOS and macOS.

Tests: xcodebuild test -project FptnVPN.xcodeproj -scheme FptnVPN -destination 'platform=iOS Simulator,OS=18.5,name=iPhone 16' -skipPackagePluginValidation CODE_SIGNING_ALLOWED=NO
…TCP keepalives

- Wire server-assigned dynamic IP addresses from C++ bridge layer down to PacketTunnelProvider across iOS, macOS, and tvOS.
- Re-apply network settings on reconnect/reassertion only if server-assigned IP changes to prevent connection disruption.
- Resolve Swift 6 strict concurrency warnings (e.g., @unchecked Sendable conformance on providers).
- Rebuild all fptn_native_lib.framework targets with Darwin TCP keepalive uncommented.
- Add localizations and the l10n_xcstrings.py export/import script.
Replace the Obj-C++/C-pointer bridge layer with direct Swift<->C++
interop across all platforms (iOS, macOS, tvOS).

- HTTPS: drop WrapperApiClientBridge in favor of a SwiftApiClient C++
  class consumed directly from Swift via std.string.
- WebSocket: replace the websocket_client_bridge_* C functions with a
  WebsocketSwiftBridge C++ class.
- Enable C++20 and SWIFT_CXX_INTEROPERABILITY_MODE/objcxx in project.yml.
- Update CMake sources and per-target bridging headers accordingly.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Reflect the current state: XcodeGen-generated project, iOS/macOS/tvOS
app+tunnel targets, net.mrmidi bundle prefix, packet-tunnel providers,
shared tunnel runtime, and the new Swift/C++ interop bridge layer.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Push notifications can be blocked by censorship whitelists when APNs
connects directly. Force APNs traffic through the FPTN tunnel by setting
includeAllNetworks=true + excludeAPNs=false on the app-side
NETunnelProviderProtocol (the only combination the OS honors for this).

Gated behind a new default-ON setting, exposed as a toggle on iOS
(Routing section) and macOS (Connection box). On macOS, excludeLocalNetworks
is also set so LAN access keeps working (iOS defaults this on). Guarded by
iOS 16.4 / macOS 13.3 availability; tvOS unaffected (API unavailable).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Translate the new "Route Push Notifications" toggle and its footer in the
iOS catalog, and the macOS toggle string. Restores the Russian translation
for the Per-App VPN footer that was orphaned when the push explanation was
merged into it.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Points the submodule at fdc6150, which fixes the per-reconnect
WebsocketClient leak (io_context drain on Stop) that was tripping the
network extension's 50MB jetsam limit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…routing

Replace VPNConnection.connectionTime (a foreground-only 1s counter) with
connectedAt: Date?, anchored to NETunnelProviderManager.connection.connectedDate.
Elapsed time is now computed on display, so the duration stays correct across
backgrounding and app relaunch instead of stalling while the foreground timer
is paused. HomeViewModel repaints the label every second and catches up after
background gaps; the start time is preserved across reasserting/reconnect so a
transport blip no longer resets the session timer.

Also remove the Per-App VPN navigation entry from Settings (renaming the
section to "Routing") and trim the now-stale per-app sentence from the footer
string in en/ru.
…oke test

Bring the dormant mrmidi iOS codebase to a buildable state on the org repo's
mrmidi-base branch (iOS-only scope):
- conan profiles: apple-clang 21->17 (conan 2.24 caps at 17 on this toolchain)
- conanfile: force zlib/1.3.1 to resolve a transitive version conflict (drift)
- add ControlPlaneSmokeTests: drives the real ApiClientBridge (libfptn) to
  verify TLS/obfuscation/login/DNS against a live server on the simulator;
  token read from gitignored .build/test-token.txt

Verified: native framework + app build & launch on iOS simulator; control
plane reaches a live server (handshake ok / login 200 / dns 200).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Was pinned to Aleksandr's team (F6YA6B56LR). Now DEVELOPMENT_TEAM reads from
${FPTN_DEVELOPMENT_TEAM} so simulator builds need no team (CODE_SIGNING_ALLOWED=NO)
and device builds set it via env/CLI once a paid account is available.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move all Apple identifiers off Aleksandr's namespace to the project's own:
- bundle IDs net.mrmidi.* -> org.fptn.* (project.yml)
- App Group group.net.mrmidi.FptnVPN -> group.org.fptn.FptnVPN (entitlements +
  shared Swift constants in SharedTunnelRuntime + per-target SharedLogSink)
- keychain service net.mrmidi.fptn.credentials -> org.fptn.credentials
- keychain access group F6YA6B56LR.group... -> ZX97GS6XNF.group.org.fptn.FptnVPN
- provider bundle id used by the app to start the tunnel -> org.fptn.*
- logger subsystems / queue labels -> org.fptn.*
macOS/tvOS renamed for consistency (not yet building on the new protocol).
Verified: iOS app + extension build for the simulator (0 errors).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…sh, correctness

Phase 1 — release blockers + security/crash:
- Stop logging the cleartext account password; drop password-length leaks
  (TokenService, KeychainHelper)
- Keep the password only in the synchronizable iCloud Keychain — strip it from
  UserDefaults and iCloud KVS, migrate+remove the legacy cleartext KVS copy, and
  clear it on logout
- Add PrivacyInfo.xcprivacy (app + extension); trim the app NE entitlement to
  packet-tunnel-provider only
- Remove the false ITSAppUsesNonExemptEncryption declaration (defer the export
  questionnaire to App Store Connect)
- Fix a shared_ptr data race in WrapperWebsocketClientBridge (lock client access
  in sendPacket/isStarted)
- Derive the keychain team prefix at runtime (no hardcoded team); env-drive the
  bundle-id namespace via ${FPTN_BUNDLE_PREFIX} + generate.sh (canonical org.fptn,
  .env.local dev override)
- Make build_fptn_lib.sh honor the Xcode configuration (Release archives are no
  longer built Debug)

Phase 2 — correctness bugs:
- Connect re-entrancy guard; getStatus/prepareStop IPC timeout via a single-resume
  gate; single-flight guard on the 1Hz speed probe
- Terminate the tunnel when the reconnect budget is exhausted (0 = infinite kept)
- Auto-select verifies candidates concurrently (off the actor), picks the
  best-ranked VERIFIED host probed with the resolved connect strategy, and falls
  back to best-TCP so it never hard-fails when servers exist
- JSON-encode the login body; delete the orphaned lifecycle FSMs + their tests

Device-verified on iPhone 14 Pro / iOS 26.5: login persistence (keychain runtime
team-prefix), data plane, reconnect churn, memory, clean disconnects, auto-connect.
Also folds in accumulated iOS working-tree work (AppIdentifiers centralization,
real throughput stats, censorship-strategy parity, libfptn convergence shim).

NOTE: FptnLib/fptn points at a local-only ios-converged commit — DO NOT PUSH this
branch until the upstream libfptn PR lands and that commit is public.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… errors

- Route dynamic user-facing strings through String(localized:) at the source
  (VPNService/ServerSelectionService/HomeViewModel/LoginViewModel/HomeView) so the
  catalog is actually consulted at runtime — previously Text(stringVar) skipped it
  and ru never displayed.
- Map raw connection failures to a small set of localized, user-friendly messages
  (invalid/expired token for 401/403, connection-blocked-try-another-bypass for
  TLS/handshake failures, cannot-reach-server for refused/timeout); log raw detail,
  display localized.
- Add Persian (fa) to CFBundleLocalizations + fa (and any missing ru) for all 132
  user-visible keys in Localizable.xcstrings (script: scripts/add_fa_localizations.py).
  Log-only/symbol keys (—, %@, %@ms, %@s) intentionally left untranslated.
- Locale-aware speed formatting (NumberFormatter, Gbps tier); CLDR plurals for the
  SNI-checker/server-scan counts (ru one/few/many/other, fa one/other).

fa is a solid first pass — recommend a native-speaker review before release.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…per-app VPN

- Bundle global.sni (264) + russia.sni (~59k) into the app Resources and wire them
  as the SNI-scanner default via a Global/Russia/Custom source picker (default
  Global; manual paste preserved). Ships in the main app, not the memory-limited
  extension. (russia.sni is large — may want a curated subset / scan cap later.)
- Add a "Connect On Demand" toggle (SettingsService.onDemandEnabled, default off);
  VPNService sets manager.onDemandRules=[NEOnDemandRuleConnect()] / isOnDemandEnabled
  accordingly before saveToPreferences — the iOS equivalent of Android always-on.
- Remove the non-functional per-app VPN: it needs an MDM/managed entitlement and
  the UI was only reachable from #Preview while perAppMode/allowedApps were dropped
  (the tunnel never read them). Deleted AppFilterView/AppFilterService/AppFilter/
  PerAppVPNMode and the dead providerConfiguration keys (0 remaining references).
- New catalog keys (en/ru/fa): Connect On Demand, Global, Russia, Custom.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nStack, About; cleanups

- Accessibility: VoiceOver label + live state value on the Connect control, labels on
  the tab items, and combined download/upload speed elements (decorative arrows folded in).
- Server list: NavigationView → NavigationStack; ProgressView during the initial scan;
  empty-state row with a pull-to-refresh hint.
- "Disconnecting…" status while the tunnel tears down (derived isDisconnecting), instead
  of flipping straight to Disconnected.
- About screen (from Settings): app version/build, account service/username (never the
  password), and the existing Website / Telegram-Bot links (no new URLs invented).
- Cleanups: untrack token_debug.py (git rm --cached; still gitignored on disk); strip
  Headers/PrivateHeaders/Modules from the embedded fptn_native_lib.framework before
  codesign so ~82 C++ headers no longer ship in the .app.
- New catalog keys (en/ru/fa): Disconnecting…, About, Version, Build, Service, Username,
  Website, Telegram Bot, Connect, Disconnect, Download/Upload speed, loading/empty rows.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ork-header strip

macOS/tvOS parity (port of iOS Phase 1/2):
- Retire the legacy cleartext password from iCloud KVS: Fptn-macOS CloudTokenSync +
  Fptn-tvOS TvCloudTokenSync now treat the synchronizable iCloud Keychain as
  authoritative and remove any residual fptn.cloud.password from KVS once observed.
- Bound reconnect: Fptn-macOS-Tunnel terminates the tunnel when the configured
  reconnect budget is exhausted (0 = infinite preserved). (tvOS tunnel has no
  reconnect loop, so nothing to bound there.)
- macOS build verified (BUILD SUCCEEDED); tvOS mirrors macOS (tvOS platform not
  installed locally to build).

Revert the Phase-5 framework-header strip: strip_framework_headers ran in
build_fptn_lib.sh on the COMPILE-TIME framework copy, but the tunnel bridging headers
#include <fptn_native_lib/...> resolve through that Headers/ dir — so a fresh/archive
build failed ("header not found"). Phase 5's iOS verify only passed because the native
framework was cached (the strip never ran). Removed it; will redo correctly as a
post-embed step on the built .app in a later pass.

Flag (out of scope): MacKeychainStore/TvKeychainStore still hardcode the team prefix
82MYMPMZF6 (iOS derives it at runtime) — follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…, throttle diagnostics, post-embed header strip

- Remove the inert IP-assignment callback path (static-tun/internal-NAT means it
  never fired; assigned IPs were always nil): dropped the ipAssignedCallback from the
  C++ shim (WrapperWebsocketClientBridge.h/.cpp), all bridge wrappers, and the
  providers (assigned/applied IP vars, the unreachable ipChanged reassert branch);
  applyNetworkSettings now uses the static tun addresses directly. Behavior-preserving
  (reasserting never re-applied settings before either; NE retains them).
- Dedup parse_censorship_strategy into FptnLib/src/common/censorship_strategy_parse.*
  (both copies were identical); included from SwiftApiClient.cpp + the WS bridge; warns
  only on a genuinely unrecognized non-empty strategy ("sni"/empty recognized as kSni).
- Throttle TunnelDiagnosticsStore I/O: O(1) FileHandle append + periodic trim (was a
  full O(n) rewrite per event); coalesce heartbeat writes to <=1/s. On-disk format +
  read API unchanged (diagnostics-store tests still pass).
- Redo the framework-header strip correctly: strip Headers/PrivateHeaders/Modules from
  the EMBEDDED framework in the built .app (project.yml Sign-FptnLib postBuildScript,
  pre-codesign), so the compile-time copy keeps headers (fresh builds work) and the
  shipped .app omits them.

Verified: fresh native rebuild + iOS-sim build SUCCEEDED; FptnVPNTests 10/10.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…0422a1)

The submodule pins the exact device-verified commit e0422a1 (upstream b170630 +
the 3 Apple build/keepalive commits). It was local-only; push it to a controlled
fork and point .gitmodules there so `git submodule update --init` resolves for
anyone fetching this branch — without waiting for the upstream apple-build-support
PR (#291) to merge.

Converge step (after #291 lands in fptn-project/fptn master): repoint the URL to
fptn-project/fptn and bump the pointer to the merged commit.

Co-Authored-By: Claude Opus 4.8 (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