Commit Graph

1425 Commits

Author SHA1 Message Date
Davide
4b4fca8344
Replace active modules count with a description ()
To get access to modules, try to avoid full Profile objects. Instead,
replace the coupled ProfileHeader occurrences with a new intermediary
ProfilePreview everywhere.

This way, a ProfileProcessor can inject the localized modules
descriptions from above with the preview() method.
2024-11-22 12:52:51 +01:00
Davide
a2f246454d
List other modules in TV active profile ()
Non-VPN profiles would otherwise be very shallow.
2024-11-22 10:56:37 +01:00
Davide
9b366dcaa0
Minor UI fixes ()
Update library with some more meaningful names for Profile accessors.

Refactor a few things about TV here and there.
2024-11-22 10:05:46 +01:00
Davide
366dc62231
Drop "Name" field from modules ()
Will rather add a "Notes" field to profiles. Module names feel
overwhelming.
2024-11-22 10:05:08 +01:00
Davide
72f6a1b1a1
Fix condition for
Was never disabling sharing for shared profiles.
2024-11-22 02:43:54 +01:00
Davide
0bff571d14
Polish TV profile view ()
- Layout with some profile details
- Add colors with adjusted brightness
- Add iOS/macOS instructions
- Add pending/sleeping markers
- Add previews
2024-11-22 02:20:41 +01:00
Davide
2636711e16
Do not disconnect active profile on remote import ()
Update library for this fix.
2024-11-22 01:15:51 +01:00
Davide
14398f5f20
Fix shared profile not updated on provider server selection ()
First, rename `force` parameter of ProfileManager.save() to `isLocal`,
because it's meant to be used when saving local profiles. In such
scenario, a profile that is also remotely shared _must_ be re-saved to
the remote repository.

This was not being done when selecting a provider server, and it could
be noticed because the other devices would only receive the iCloud
update after editing the profile and re-doing a manual "Save". Only at
that point would the new profile be re-shared on iCloud.
2024-11-22 00:46:58 +01:00
Davide
b0c98ed27a
Lint XCTFail() 2024-11-21 23:01:08 +01:00
Davide
3d2a543a54
Fetch products from scratch each time ()
Do not risk getting products that may have been removed from sale.
2024-11-21 22:58:24 +01:00
Davide
c5bdb9e363
Group IAP extensions into ad-hoc folder 2024-11-21 19:44:37 +01:00
Davide
5bec574841
Test MigrationManager ()
Decouple importer from ProfileManager.
2024-11-21 19:44:20 +01:00
Davide
d74c94c125
Fix missing betaChecker arg in IAPManager 2024-11-21 19:10:36 +01:00
Davide
842375ffce
Test IAPManager ()
Refactoring:

- Split tests
- Report purchase to receipt reader (real impl does nothing)
- Abstract BetaChecker from now TestFlightChecker

Bugfixing:

- Fix duplicated code in fetchLevelIfNeeded()
- Fix observeObjects() missing .didUpdate publisher
2024-11-21 18:57:32 +01:00
Davide
2478fb204b
Test ExtendedTunnel ()
Decouple .connectionStatus to TunnelStatus.withEnvironment() for easy
testing.
2024-11-21 16:48:12 +01:00
Davide
a93fcd4c66
Test ProfileManager ()
With some housekeeping.

Bugfixing:

- Do NOT skip empty remote profiles, allow removal when mirroring
- Look up profile in all profiles, not just filtered
- Posptone non-included profile removal

Refactoring:

- Rename ProfileProcessor to InAppProcessor
- Provides ProfileProcessor + TunnelProcessor protocols
- willSave -> willRebuild (because not always called on save)
- Notify ProfileManager import events
2024-11-21 16:03:57 +01:00
Davide
c7cef70eed
Move IAP extensions to CommonLibrary
Leave CommonIAP agnostic of business models.
2024-11-21 12:09:06 +01:00
Davide
4466616339
Fix compile errors from 2024-11-21 10:47:46 +01:00
Davide
b45f9c23fe
Rearrange targets for unit testing ()
- Move and rename entities
- Split protocols and default extensions
- Move in-app entities to CommonIAP target
2024-11-20 18:05:47 +01:00
Davide
2ccd3052ac
Log verification failure in tunnel 2024-11-19 22:46:18 +01:00
Davide
74ed93a966
Check provider requirements before connecting ()
E.g. interactive login must be presented afterwards.
2024-11-19 20:51:55 +01:00
Davide
d8545a01b4
Update profiles requirements on new in-app receipt ()
Required features were only updated when reloading the local profiles,
but they may also change on in-app events due to changes in eligibility.
2024-11-19 19:44:31 +01:00
Davide
9528cecc27
Verify profile before presenting interactive login ()
If the profile is ineligible for features other than .interactiveLogin,
the interactive prompt would still be presented, but the user would hit
the paywall right afterwards.
2024-11-19 11:46:42 +01:00
Davide
35c10309a7
Reload receipt on purchase ()
StoreKit ProductView performs the purchases internally without calling
IAPManager.purchase(), which causes the IAPManager state to be
momentarily outdated.

Leverage PaywallView.onComplete() to reload the receipt and eventually
trigger IAPManager.objectWillChange, so that the app is immediately
unlocked on a successful purchase.
2024-11-19 11:36:36 +01:00
Davide
bac1d494a4
Upscale upgrade icon only on macOS 2024-11-19 11:18:00 +01:00
Davide
d78456bb90
Show upgrade icon in profiles list ()
Visually clarify that a profile requires a purchase to be enabled.

- Implement AppFeatureRequiring in Profile
- Refactor IAPManager.verify() accordingly
- Pre-compute required features in ProfileManager via ProfileProcessor
2024-11-19 08:55:41 +01:00
Davide
1536551922
Prepare WireGuard for provider selector ()
- Omit configuration on creation to show provider selector
- Take out ConfigurationView like OpenVPN
2024-11-18 17:49:47 +01:00
Davide
89d7af4df7
Rethink eligibility checks ()
- Allow unrestricted save, but show PurchaseRequiredButton
- Warn however about paid features (FIXME)
- Redesign features in paywall
- Strip already eligible features from paywall
- List required features in restricted alert
- Localize feature descriptions
- Review propagation of paywall modifiers/reasons

Extra:

- Move more domain entities from UILibrary to CommonLibrary
- Default on-demand policy to .any (free feature)
- Fix modals not reappearing after closing with gesture
- Extend UILibrary start-up assertions
2024-11-18 17:43:01 +01:00
Davide
e82dac3152
Clear previous remote import task ()
Previous task was never nil-ed out on completion.
2024-11-18 10:56:54 +01:00
Davide
a3dad4e445
Observe tunnel in grid/list
Can notice in Simulator not updating installed profile.

Regression from 
2024-11-18 09:10:15 +01:00
Davide
db592944ec
Rename module builders as functions 2024-11-17 22:30:22 +01:00
Davide
8e7f3e80ce
Update library with provider in modules ()
Move provider metadata from profile to module.
2024-11-17 22:27:02 +01:00
Davide
20813ac77e
Parametrize app name in strings 2024-11-17 18:48:40 +01:00
Davide
00ba67f135
Refactor buttons/menus
- Add divider in add profile menu
- Reuse trailing dots
2024-11-17 18:45:04 +01:00
Davide
e58726d0af
Do not discard migrated profiles in TestFlight ()
Users would lose those profiles on downgrade to v2.
2024-11-17 18:21:37 +01:00
Davide
a2f17cc905
Use long dash for excluded migration profiles 2024-11-17 16:26:22 +01:00
Davide
01e0559060
Fix some minor bugs
- Observe profiles on Simulator
- Dismiss on error when fetching migratable profiles
- Improve migration logs ambiguity
2024-11-17 15:47:19 +01:00
Davide
c93a43702c
Improve migrate design ()
- Mention iCloud in informational message
- Redesign to always show message despite no migratable profiles
- Refine the looks
2024-11-17 14:02:40 +01:00
Davide
22e8e659e4
Discard old profiles once migrated ()
Otherwise, they would reappear if the migrated profiles are deleted from
the new store.
2024-11-17 11:40:49 +01:00
Davide
d3e344670b
Resolve excessive profile reloads ()
Optimize ProfileManager in several ways:

- Refine control over objectWillChange
- Observe search separately
- Store subscriptions separately (local, remote, search)
- Fix multiple local updates on save/remove/foreground (updating
allProfiles manually)
- Update the library with more optimized NE reloads
- Cancel pending remote import before a new one
- Yield 100ms between imports
- Reorganize code

Extras:

- Only use background context in provider repositories
- Externalize tunnel receipt URL, do not hardcode BundleConfiguration
- Improve some logging

Self-reminder: NEVER use a Core Data background context to observe
changes in CloudKit containers. They just won't be notified (e.g. in
NSFetchedResultsController).

Fixes 
2024-11-17 11:34:43 +01:00
Davide
9e5beff23a
Perform migrate + import in one step ()
- Drop the .importing / .imported steps
- Animate rows re-sorting during process
- Rephrase some strings better
- Test fake migration with launch argument
2024-11-16 21:16:25 +01:00
Davide
afa22719bf
Improve migrate header on iOS ()
Refactor into more subviews.
2024-11-16 15:07:47 +01:00
Davide
589f2f62e0
Revert "Modernize check for TestFlight build" ()
Reverts 
2024-11-16 13:54:11 +01:00
Davide
3737560851
Add the option to migrate old profiles ()
Finalize migration flow:

- Add entry to "Add" menu
- Suggest to migrate old profiles when there are no profiles
- Add informational message
- Keep included profiles on top
- Allow deletion of migratable profiles
- Fix duplicated Form in preview
- Rename views and models

Improve some Theme modifiers:

- Empty message with full screen option
- Progress modifier with custom view
- Confirmation dialog with custom message
2024-11-16 12:29:03 +01:00
Davide
9ca103e949
Refactor modal size to be a modifer parameter ()
Way more flexible.
2024-11-15 23:32:54 +01:00
Davide
b08243949c
Nested ZStack modifiers in progress/empty modifiers ()
Merge into a single modifier because they are used together.
2024-11-15 16:33:26 +01:00
Davide
55bb2e79c9
Modernize check for TestFlight build ()
Closes 
2024-11-15 15:34:47 +01:00
Davide
09e894dd60
Add explicit environment objects to TableColumn ()
For some reason, Table doesn't seem to inherit the environment in some
cases. Reapply environment to each TableColumn (only Theme is required).

Work around what clearly seems to be a SwiftUI bug.

Fixes 
2024-11-15 01:47:52 +01:00
Davide
962ffdf678
Sort servers by .serverId in repository 2024-11-15 00:24:36 +01:00
Davide
6472ac0502
Revert "Sort provider servers on iOS"
This reverts commit 2b3961aef0.
2024-11-15 00:22:06 +01:00
Davide
2b3961aef0
Sort provider servers on iOS 2024-11-15 00:00:57 +01:00
Davide
b52e3c779b
Clean up and rename beta receipt variables 2024-11-14 22:52:52 +01:00
Davide
5818b36cda
Copy release receipt to tunnel ()
Not just the provided one, because it could be the sandbox receipt.

Fixes  further.
2024-11-14 19:42:38 +01:00
Davide
596f5702c9
Disable migrations UI 2024-11-14 19:15:10 +01:00
Davide
f6361ebf06
Fix "Purchase required" in TestFlight ()
- Define separate IAPManager instances for app and tunnel (different
receipt URLs)
- Copy app receipt URL over to tunnel before install/connect
- Use AppTransaction to get original build number so that
FallbackReceiptReader is also much simpler now

Fixes 
2024-11-14 19:12:51 +01:00
Davide
341ae97f50
Put migrated on-demand last 2024-11-14 15:19:07 +01:00
Davide
615f7d47bd
Import migrated profiles ()
Finalize basic flow started in .
2024-11-14 15:11:25 +01:00
Davide
114e1abe12
Add initial migration UI ()
Repurpose LegacyManager as MigrationManager. Present initial migration
UI from "+" menu in app home.

Different styles:

- iOS → Section / ForEach
- macOS → Table
2024-11-14 11:02:26 +01:00
Davide
7ef780b8a4
Decouple Mac settings from AppMenu ()
- 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
6495f51dc1
Unify macOS toggles in menu/settings ()
Show in both:

- Launch on login
- Keep in menu bar
2024-11-13 22:04:34 +01:00
Davide
91344c1294
Only guard remote fingerprint if local has any ()
Otherwise, it would never import remote profiles w/o a fingerprint.

Scenarios (must test in ):

- No local profile → Import
- Local profile has no fingerprint → Import
- Local profile has fingerprint
  - Remote profile has no fingerprint → Skip
  - Remote profile has same fingerprint → Skip
  - Remote profile has different fingerprint → Import
2024-11-13 21:46:14 +01:00
Davide
0a51d1a1f6
Strip package dependencies ()
The Library package offers the PassepartoutImplementations target for
OpenVPN/OpenSSL and WireGuard/Go, but it doesn't need it itself. Only
the main app does, so move the dependency there.

On the other side, drop the potentially problematic AppUI meta target.
Move platform filters to the Xcode project.

Indirectly fixes a crash with Xcode 16 Previews on iOS (forced to use
legacy previews before):

https://forums.developer.apple.com/forums/thread/756681
2024-11-13 21:05:53 +01:00
Davide
2d698cabfe
Reuse modifier for navigation stacks with close button ()
Refactored:

- iOS
  - VPNProviderServerView
- macOS
  - AboutView
  - ProfileSplitView
- Both
  - ProfileCoordinator
  - VPNProviderServerCoordinator
2024-11-13 19:17:00 +01:00
Davide
1b9b9cbd5c
Import initial remote profiles ()
Crucial on clean install.
2024-11-13 12:11:29 +01:00
Davide
008b78cc7c
Upgrade to Xcode 16 ()
- Address further restrictions on actor-isolation by using `nonisolated`
on:
  - Combine subjects
  - Core Data context/controller
  - Blocks
- In previews using inline `@State`, create a custom view instead
- Use `@retroactive` in l10n extensions
- Fix compile error in WireGuardKit
2024-11-13 12:07:30 +01:00
Davide
54f4364c33
Split test jobs ()
Move Core Data tests out of the Library package so that we can still use
the more efficient `swift test` for most tests.

Create a PassepartoutTests target only for tests that require
`xcodebuild`, like Core Data tests.

Eventually:

- PRs only run SwiftPM tests
- Releases run ALL tests with `scan` before `gym`
2024-11-12 18:35:44 +01:00
Davide
e514ade036
Add logic to migrate v2 profiles ()
Will add UI separately.

Fixes 
2024-11-12 16:42:19 +01:00
Davide
83d77fafbb
Expand selected server country on iOS ()
Add isExpanded binding to DisclosureGroup.
2024-11-11 20:21:02 +01:00
Davide
4ac524eb01
Exclude interactive login from v2 () 2024-11-11 20:10:27 +01:00
Davide
1dfb013115
Implement some CustomDebugStringConvertible
- AppFeature
- AppProduct
- ProviderID
2024-11-11 19:45:56 +01:00
Davide
fb0a9262c0
Fix double reload of selected provider server ()
Skip initial nil filter, overlaps with initial .reloadServers() in task.

Fixes 
2024-11-11 16:11:09 +01:00
Davide
0686650ccf
React on providers eligibility ()
Check .providers eligibility in tunnel to prevent from starting if
profile has an active provider module. Do not alter original profile.
2024-11-11 12:50:26 +01:00
Davide
30ccd58d4a
Wait for initial profiles ()
Show progress view until initial local/remote profiles are fetched. May
visually improve later.
2024-11-11 12:08:22 +01:00
Davide
d6ac4cd818
Use hidden icons for stable alignment ()
Align SF Symbols to the text baseline, but also include all possible
icons in ProfileAttributesView to precalculate a stable height for the
HStack.
2024-11-10 20:54:00 +01:00
Davide
7719630cdd
Limit tunnel updates ()
- Do not observe tunnel in grid/list
- Only observe .$currentProfile for grid selection
- Move row tunnel updates to MarkerView
- Debug InstalledProfileView
2024-11-10 19:39:43 +01:00
Davide
bd4aeed97a
Disable animations on installed profile view
Fixes 
2024-11-10 19:33:47 +01:00
Davide
44468b5d1f
Fix regression about handling of Core Data duplicates ()
Regression in  due to how NSFetchedResultsController was refactored.
Duplicated entities were not excluded from mapping.

Could "crash" the app with these easy steps:

- Pick a profile
- Unshare the profile on iOS
- Unshare the profile on macOS
- Re-share the profile on iCloud on both iOS and macOS
- Save the profile simultaneously on iOS/macOS
- Assertion failure due to duplicates in
ProfileManager.reloadRemoteProfiles() → "Remote repository must not have
duplicates"
2024-11-10 18:13:59 +01:00
Davide
21340e9f56
Rewrite AppContext event handlers ()
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
fdbed7442c
Unrestrict some features in .beta ()
Add these features to the .beta user level:

- .interactiveLogin
- .sharing

Fixes 
2024-11-10 16:53:01 +01:00
Davide
3a5e3889d3
Add more view modifiers ()
1. ThemeProgressViewModifier to replace content with a progress view
while a condition is active
2. ThemeEmptyContentModifier to replace content with a message if an
empty condition is met
3. Replace .opacity(bool ? 1.0 : 0.0) with .opaque(bool)

Reuse:

- 1 in PaywallView and DonateView
- 2 in ProfileContainerView
2024-11-10 12:00:07 +01:00
Davide
e07833b2a4
Revisit in-app eligibility for iCloud sharing ()
Restore .sharing feature:

- Merge "Apple TV" into "iCloud" section
  - "Enabled", disabled if ineligible for .sharing
  - "Apple TV", disabled if ineligible for .appleTV || !isShared
- Footer about TV restrictions

Paywalls:

- "Share on iCloud" if ineligible for .sharing
- "Drop TV restriction" if eligible for .sharing but not for .appleTV
  - Applies to full version products (user level 2)
  - Suggest Apple TV product

Restrictions:

- Toggle CloudKit sync on remote repository based on .sharing
eligibility
- Do not start tunnel on Apple TV if ineligible for .appleTV

Fixes:

- Incorrect zip() publishers in remote repository
- Resolve duplicates in Core Data, first profile wins sorted by
lastUpdate descending
- Reload receipt on OOB IAPManager events
2024-11-09 15:20:59 +01:00
Davide
d209b0d9b0
Fix compile error on iOS for previous commit 2024-11-08 15:51:56 +01:00
Davide
7c27125dd7
Decouple library from PassepartoutKit implementations ()
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
fed3a9b7d1
Drop old FIXME 2024-11-07 23:56:44 +01:00
Davide
bfc0be45ae
Hide donation link in beta 2024-11-07 23:10:26 +01:00
Davide
8fbccc6d80
Add donations UI and in-app error handling ()
- Reuse same product views from paywall
- Handle errors in fetch products
- Hide views on fetch products error
- Disable views during purchase

Closes 
2024-11-07 23:02:10 +01:00
Davide
2c1ccbcbfd
Finalize paywall UI ()
- Use StoreKit views when available
- Offer one-time purchase
- Recurring subscriptions for all features
- Restore purchases

Remove .siri (Shortcuts), now free.

Closes 
Closes 
2024-11-07 18:27:36 +01:00
Davide
8ef1e7fbe9
Refactor theme section and styles ()
Provide shortcut for section with single row. Redesign Settings
accordingly.
2024-11-07 15:50:19 +01:00
Davide
21c1bbdf0d
Fix missing profile attributes initialization ()
Also, log them better during remote import.
2024-11-07 13:23:47 +01:00
Davide
22a5cb9af2
Update FIXMEs 2024-11-07 12:48:10 +01:00
Davide
b128b3bf13
Erase shared profiles from iCloud correctly ()
Do not delete CloudKit zone. Instead, delete Core Data entities and let
sync do the rest. It's also a "more standard" approach.

Deleting the zone right after the entities legitimately makes deletion
ineffective, because it probably spoils sync.
2024-11-07 12:43:24 +01:00
Davide
142efa84d0
Always read local receipt in TestFlight ()
The condition came from v2, but the flow was different. Drop the
condition because it would always fail in TestFlight for macOS, where
sandbox and release receipts have the same URL.
2024-11-07 11:46:41 +01:00
Davide
c32dcd6565
Fine-tune important logging categories ()
- .App.profiles for profiles management
- .App.iap for in-app purchases
2024-11-07 11:33:20 +01:00
Davide
5949ff1508
Read receipts in a serial fashion ()
Deal with reentrancy issues by ensuring serial execution.
2024-11-07 11:25:40 +01:00
Davide
9abbc6cde2
Redefine in-app receipt strategy ()
1. TestFlight: look for release receipt
2. Primary receipt (StoreKit) with build from local
3. Local receipt
2024-11-07 09:54:51 +01:00
Davide
abe4c779b8
Improve startup time ()
Profiles were loaded after reading receipt, which took at least a second
on iOS.

Potential regression from 
2024-11-07 01:48:39 +01:00
Davide
68df6066ba
Improve configuration on app launch/active ()
- 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 ()
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
d5ac785bb8
Simulate in-app purchases ()
Integrate in-app helper into IAPManager and simulate purchases with an
in-memory receipt.
2024-11-05 18:55:57 +01:00
Davide
9351ceeb6a
TV not presenting interactive login on "Connect" ()
The side panel was not shown when interactive login was triggered by the
active profile on "Connect".
2024-11-05 16:13:03 +01:00