Commit Graph

716 Commits

Author SHA1 Message Date
Davide
4e43c14cc6
Update copyright 2025-01-15 20:22:52 +01:00
Davide
ea8176047d
Read user level in tunnel too 2025-01-06 21:50:38 +01:00
Davide
24ddb270aa
Skip backup repository on simulator (#1048)
ProfileManager is behaving crazy because on simulator local and backup
repository point to the same container.
2025-01-04 10:45:51 +01:00
Davide
3706268bf9
Drop broken macOS code to get SSID (#1042)
Related to #497
2024-12-23 22:47:33 +01:00
Davide
3e04f03b01
Skip local receipt on Apple TV (#1041)
App/Tunnel behavior is inconsistent.
2024-12-22 12:24:20 +01:00
Davide
f112ea8061
Save last used profile (#1036)
Especially useful on macOS and tvOS where Network Extension does not
retain this information when the profile is disabled. On these
platforms, there's no native way to tell the last used profile, so save
it to UserDefaults and fall back to it when tunnel.currentProfile is
nil.
2024-12-21 22:39:55 +01:00
Davide
0d3af046b4
Fall back to production if beta receipt is missing (#1031)
When level is .beta, it was relying on beta receipt exclusively without
falling back to production receipt.

This was preventing the sandbox receipt ("production" in TestFlight)
from being read unless the AppUserLevel was explicitly set to .freemium
(0).
2024-12-20 21:09:02 +01:00
Davide
59e4e2e4ab
Hide profiles until remote repository is ready
Regression from #1029
2024-12-20 10:39:39 +01:00
Davide
f8e623e1fe
Fix regressions with CloudKit synchronization (#1029)
The remote container is shared by ProfileManager and
PreferencesManager, but it must be the same for CloudKit sync
to work properly.

Externalize the logic of onEligibleFeatures() so that the
AppContext singleton can update the managers (and their
repositories) with the new remote store.

Now that the remote profile repository is reloaded every time that
eligible features change, the .removeDuplicates() may also be
restored. Just add a .dropFirst() to skip the initially empty
value of eligible features. Even when features are eventually empty,
a value is always emitted after IAPManager.reloadReceipt()

Lastly, enable Core Data lightweight migration.

Regressions from #1017
2024-12-20 10:05:07 +01:00
Davide
1f81c3dfea
Resolve NEVPNErrorDomain error and profile duplication on first import (#1026)
Fixes #1025
2024-12-18 17:26:35 +01:00
Davide
c527171957
Simplify products (#1024)
Only offer compensations to former purchasers:

- .appleTV to .full purchasers
- .full to .appleTV purchasers

Always suggest .fullTV to new purchasers.

Finally rename:

- .full to .iOS_macOS
- .fullTV to .allFeatures (lifetime)
2024-12-18 10:15:58 +01:00
Davide
c422f06713
Drop malformed keychain group
Fixes #725
2024-12-17 18:29:00 +01:00
Davide
789f6ad515
Unify paywall and eligibility (#1018)
- Present paywall only on save/connect because PurchaseRequiredButton
instances were presenting paywalls with partial requirements
- Retain PurchaseRequiredButton just to flag paid features visually, but
do nothing on tap
- Suggest paywall products via IAPManager and required features, rather
than from views
- Handle .sharing/.appleTV requirements in ProfileCoordinator, rather
than in StorageSection
- Discontinue platform purchases, but treat as full if iOS + macOS
2024-12-16 21:45:36 +01:00
Davide
ffb8829f4f
Reorganize Core Data containers (#1017)
Before anything, remove any code related to App Group containers from
tvOS target because they are not available. Include the beta receipt
override, it's broken for that reason.

In short:

- Store all Core Data containers locally. Do not use the App Group for
Core Data for consistency across platforms.
- Store logs in the App Group on iOS/macOS, but locally on tvOS (see
`urlForCaches`).

Then, rather than one container per model, merge models into:

- Local: Providers
- Remote: Profiles + Preferences (now in the same CloudKit container)

Reuse the remote model for backups too.

This change is safe because:

- Local profiles are stored via Network Extension in the keychain, not
Core Data
- Remote profiles are re-imported via CloudKit sync
- Providers are re-downloaded on first use
- Preferences are lost, but they are "cheap" data
- Profile backups are lost, but they were hidden anyway
2024-12-15 20:20:33 +01:00
Davide
3ecb308d57
Resolve Apple TV crashing on launch (#1016)
Create preferences container locally because tvOS cannot write to App
Group container.

Fixes #1007
2024-12-15 13:49:39 +01:00
Davide
d0f25d6d6e
Add translations for the former 12 languages (#1005)
Closes #588
2024-12-12 23:32:27 +01:00
Davide
f441a2eed0
Autogenerate screenshots for all platforms (#998)
Unframed for now.

- Split Main/TV targets
- Extend ProfileManager for screenshot scenarios
- Rename UITesting to UIAccessibility

Active profile looks very big on TV simulator, temporarily commented
`.padding(.top, 50)` in ActiveProfileView.

Closes #974
2024-12-10 18:06:23 +01:00
Davide
6f9c78b257
Track module preferences history in Core Data (#994)
Restore CDModulePreferencesV3 to track the history of module prefrences.

This way, excluded endpoints may be saved globally to Core Data as a
starting point. Then in Profile.userInfo we only save the relevant
exclusions for the current configuration.

The .excludedEndpoints relationship is therefore moved out of
CDProviderPreferencesV3.

Further refactoring:

- ModuleViewParameters now includes a ModulePreferences observable that
module views can observe
- Tunnel doesn't need access to PreferencesManager anymore (exclusions
are in Profile.userInfo)
2024-12-10 14:13:10 +01:00
Davide
93a15cd766
Review incorrect behavior in preferences (#989)
- Save/rollback was done outside of MOC
- Use different contexts for module/provider preferences
  - Save providers → also saves modules
  - Discard modules → also discards providers
- Use background context because it's not automatically merged (can
rollback)
- Expose ModulePreferences in OpenVPNView as StateObject
- Rework Blacklist to a more reusable ObservableList
- Reapply #988
2024-12-09 08:44:13 +01:00
Davide
edaa151911
Fix compile errors from #984 2024-12-08 19:02:31 +01:00
Davide
f7013a98a9
Separate App/Tunnel responsibilities (#984)
Sort out the increasing mess coming from:

- AppContext*
- Dependencies
- Shared*

by doing the following:

- Keep in the "Shared" folder only the entities actually shared by
App/Tunnel
  - Create TunnelContext
  - Move AppContext and related to the App/Context folder
  - Move TunnelContext and related to the Tunnel/Context folder
- Delete Shared+* extensions, use AppContext/TunnelContext singletons
from the app
- Create a Dependencies factory singleton to create entities in a single
place
  - Split extensions by domain
- Make it clear with `func` vs `var` when a dependency method returns a
new instance
2024-12-08 18:56:39 +01:00
Davide
dfae6afcb4
Store complex preferences to Core Data (#981)
Replace favorites entities with a PreferencesManager, that returns
observables for:

- Module preferences (by module UUID)
- Provider preferences (by ProviderID)

Automate preferences availability in:

- Module views (empty for now)
- VPN server view (favorites)

Synchronize preferences by making this a CloudKit container. Preferences
are also available in the Tunnel by storing the container in the App
Group.
2024-12-06 11:24:51 +01:00
Davide
63496725c8
Transparent logo in version screen (#963)
Looks better on macOS and allows gradient on tvOS.
2024-11-28 22:59:37 +01:00
Davide
2a467e0c7e
Separate AppContext for previews and UI testing (#961)
Clarify the use of contexts:

- **Production** (.shared)
- **Previews** (.mock → .forPreviews)
  - ONLY use it in UILibrary for, well, previews
  - This context has dumb profiles with UUIDs as names
  - Registry is fake
- **UI Tests** (.forUITesting)
  - Add new context for UI testing
  - Selected based on command line arguments
  - This context has mock data tuned for decent screenshots
  - Registry is real

Share the same InAppProcessor in .shared and .forTesting contexts
because the app behavior was inconsistent regarding e.g. in-app
purchases.
2024-11-28 17:31:17 +01:00
Davide
962361cb9f
Automate screenshots via UI tests (#960)
Ready for screenshots generation, except for the tests themselves and
the TV target.

- More customizations while UI testing
  - Act as full version user in IAPManager
  - Override layout with default to .grid if isBigDevice
  - Show module names in profile list/grid
- Improve mock Profile/ProfileManager
  - Meaningful profile names
  - iCloud/TV icons
  - Initial modules
- Improve XCTest extensions
  - Screenshot destination (attachment/temporary)
  - Screenshot target (window/sheet)
  - Print saved temporary URL at the end (may help with CI)
  - Append device name to screenshot filename
- Tests
- Refactor actions with the [Page Object
pattern](https://swiftwithmajid.com/2021/03/24/ui-testing-using-page-object-pattern-in-swift/)
  - Perform iPad screenshots in landscape
  - Split simple flow tests and screenshots
  - Add "Connect to" test

Closes #681
2024-11-28 15:51:03 +01:00
Davide
581673c617
Add target for UI tests (#959)
Create UITesting target with:

- AppCommandLine/AppEnvironment: strongly typed refactoring of PP_*
environment values
- AccessibilityInfo: identifies and locates elements for UI testing

Make the app behave differently when launched with `.uiTesting`, and
expose the flag to SwiftUI via `.environment(\.isUITesting)` to:

- Use the mock AppContext
- Skip onboarding

Add PassepartoutUITests target with two screenshot tests:

- Connected screen
- Profile modal
2024-11-28 01:30:26 +01:00
Davide
bad9e8b58e
Add TV settings tab (#921)
Includes:

- Credits
- Donations
- Diagnostics
- Version

Had to:

- Wrap tab view into a NavigationStack for full-screen navigation
- Take out navigation titles of about subviews
- Customize donations view layout with modifier
- Fix credits and debug log to support scrolling

Closes #914
2024-11-23 23:01:11 +01:00
Davide
2a46173169
Move more views to UILibrary (#919)
- Move about subviews to UILibrary
- Refactor about to single coordinator + platform views
- Refactor debug log to single view + content views
- Take out debug log routes from about routes
- Rename Settings* to Preferences*
- Reuse empty modifier in debug log
- Fix a visual bug in .themeTrailingValue() (extra Spacer)

Preparation for #914
2024-11-23 19:26:33 +01:00
Davide
a301806ac7
Share migrated TV profiles (#918)
Fixes #912
2024-11-23 13:33:02 +01:00
Davide
4466616339
Fix compile errors from #900 2024-11-21 10:47:46 +01:00
Davide
7ef780b8a4
Decouple Mac settings from AppMenu (#865)
- Rename AppMenu.Model to MacSettingsModel for global reuse in macOS app
- Fix compile errors on iOS
2024-11-13 22:35:50 +01:00
Davide
e514ade036
Add logic to migrate v2 profiles (#854)
Will add UI separately.

Fixes #642
2024-11-12 16:42:19 +01:00
Davide
21340e9f56
Rewrite AppContext event handlers (#839)
Loading remote profiles before local profiles may cause duplicated NE
managers. This happened because if local profiles are empty, any remote
profile is imported regardless of their former existence in the local
store. The importer just doesn't know.

Therefore, revisit the sequence of AppContext registrations:

- First off
- Skip Tunnel prepare() because NEProfileRepository.fetch() does it
already
- NE is both Tunnel and ProfileRepository, so calling tunnel.prepare()
loads local NE profiles twice
- onLaunch() - **run this once and before anything else**
  - Read local profiles
  - Reload in-app receipt
  - Observe in-app eligibility → Triggers onEligibleFeatures()
  - Observe profile save → Triggers onSaveProfile()
  - Fetch providers index
- onForeground()
  - Read local profiles
  - Read remote profiles, and toggle CloudKit sync based on eligibility
- onEligibleFeatures()
  - Read remote profiles, and toggle CloudKit sync based on eligibility
- onSaveProfile()
  - Reconnect if necessary
2024-11-10 17:51:28 +01:00
Davide
7c27125dd7
Decouple library from PassepartoutKit implementations (#834)
Move the following dependencies:

- OpenVPN/OpenSSL
- WireGuard/Go

up the chain until the main App/Tunnel targets, so that UILibrary and
CommonLibrary can abstract from these unnecessary details. Instead, give
module views access to generic implementations via Registry.

Incidentally, this fixes an issue preventing TV previews from working
due to OpenSSL linkage.
2024-11-08 12:37:09 +01:00
Davide
68df6066ba
Improve configuration on app launch/active (#821)
- Centralize context initialization/refresh in platform-specific app
delegates
- Prevent multiple calls to .onApplicationActive()
- Simplify local/remote profile fingerprint comparison
- Revert to always replacing Core Data entities
- The remote store somehow ended up having duplicates, which caused
repeated imports of remote profiles due to randomly different
fingerprints
- Optimize reload of in-app receipt
2024-11-06 18:42:42 +01:00
Davide
d8c4e87239
Refactor in-app entities for StoreKit/Kvitto integration (#820)
Refactoring:

- Get receipts from StoreKit Transaction.currentEntitlements
- Search for the originally purchased build in the local receipt anyway
(Kvitto)
- Fall back to release receipt (Kvitto), if any, for feature eligibility
in TestFlight builds
- Parse and verify expiration date in subscriptions
- Decouple in-app identifier composition from BundleConfiguration
- Fix user level features only applied when a receipt was not found

Testing:

- Add StoreKit configuration
- Fake purchases with PP_FAKE_IAP
- Fake user level with PP_USER_LEVEL

Then for reactive receipt reload, detect app activation differently:

- iOS/tvOS on .scenePhase
- macOS on launch and NSWorkspace.didActivateApplicationNotification

As to features:

- Credit former "Full version" purchasers with all current AND future
features, except the Apple TV
2024-11-06 13:20:12 +01:00
Davide
1cb46e066c
Encapsulate behavior on app active (#812)
Implement inside AppContext.
2024-11-05 10:41:02 +01:00
Davide
f3d13d0cdf
Refactor AppContext creation and profile processing (#810)
Streamline initialization of AppContext objects without singletons,
especially because some are interconnected.

Rethink ProfileProcessor to be the only gateway of profile processing
for:

- Include
- Save
- Connect

Provide closures with access to the IAPManager for eligibility checks.

Finally, take a ProfileProcessor parameter in:

- ProfileManager (for isIncluded and willSave)
- ExtendedTunnel (for willConnect)

so that it's used implicitly without having to put it into the SwiftUI
environment.

Other than that:

- Move AppError to CommonLibrary
- Skip decoding of attributes from Core Data because they are already
part of the profile
2024-11-04 23:34:22 +01:00
Davide
0c66050726
Review ProfileManager observation logic (#809)
- Perform profiles removal in a single publisher, in
reloadRemoteProfiles() after importing remote profiles
- Only force a new lastUpdate/fingerprint if profile is saved locally,
DO NOT alter them if imported from remote repository because this would
cause a re-save on iCloud
- Profiles were purged twice on launch in the main macOS app
2024-11-04 10:10:17 +01:00
Davide
070b661c43
Refactor library targets (#799)
Renames:

- AppUI → UILibrary
- AppUIPlatform → AppUI (conditional umbrella) + AppUI[Main|TV]
- APILibrary/CommonLibrary/UtilsLibrary → Common[API|Library|Utils]

Dependencies:

- AppUI → UILibrary
- UILibrary → AppLibrary, CommonAPI
- AppLibrary → CommonLibrary, CommonUtils
2024-11-02 10:11:59 +01:00
Davide
590b2790fa
Revisit overuse of EnvironmentObject (#794)
The biggest issue is the hidden and scattered use of both Tunnel and
ConnectionObserver. Only use the latter, and rename it to ExtendedTunnel
for being now a full wrapper around Tunnel (e.g. for .connectionStatus).

In general, restrict the use of EnvironmentObject to:

- Theme
- IAPManager
- ProfileProcessor
- ProviderManager

Always be explicit about:

- ProfileManager
- ExtendedTunnel

Contextually, move some UI entities to the base AppUI target.
2024-11-01 09:47:50 +01:00
Davide
e7666e6097
Fix previout commit on macOS 2024-10-31 12:23:00 +01:00
Davide
9f22053fa9
Fix lifecycle of environment objects (#790)
#779 was happening because environment objects were set on contentView,
which is not the _outmost_ root view. This clarifies why the Theme
object was not being found in ThemeLockScreenModifier.

Also, do not hardcode LogoView as lock view.
2024-10-31 11:14:39 +01:00
Davide
237277d4db
Do some refactoring in AppUI targets (#789)
- Refactor AppUI initialization in all platforms (sort of template
method pattern)
- Make AppMenu specific to macOS by wrapping it into a folder for
consistency
- Add SizeClassProviding for repeated checks on hsClass/vsClass

Fixes #659
2024-10-31 10:02:21 +01:00
Davide
dcdb03a735
Improve country flags (#786)
Leverage the convenience of FlagKit. Update credits too.
2024-10-30 19:11:58 +01:00
Davide
7f3d897818
Improve macOS window lifecycle (#780)
- Let the user close the window, the app will just remain alive in the
status bar
- Accordingly, replace "Confirm quit" preference with the option to stay
alive in the status bar
- Add "About..." item
2024-10-30 10:37:45 +01:00
Davide
9d6dfe6a76
Fix crash on lock screen (#779)
Missing environment Theme in LogoView.

Probably broken in #775
2024-10-30 10:18:39 +01:00
Davide
7e2bd01c36
Omit lock screen on macOS 2024-10-29 14:44:00 +01:00
Davide
944d6f8c28
Refactor AppUI for TV target (#775)
Split AppUI into AppUI and AppUIMain to allow for a new, simplified
AppUITV target tailored for the Apple TV.

As a PoC, present a view with a list of the shared profiles.
2024-10-29 14:30:41 +01:00
Davide
41de48789e
Show in Mac status bar plus Login Item (#773)
Add a status menu via SwiftUI MenuBarExtra where to:

- Show/hide app
- Launch on login via "Login Item" target
- Toggle profiles on/off

Only weird that the login item is not added to the list of "Open at
Login", but to "Allow in the Background", see
https://github.com/pilotmoon/Scroll-Reverser/issues/165

Requires some refactoring to bring AppContext initialization to the
AppDelegate.

Fixes #617
Fixes #482 
Fixes #696 
Fixes #505
2024-10-29 11:40:11 +01:00