Commit Graph

312 Commits

Author SHA1 Message Date
Jamil
4e61ba9582 fix(apple): set new project version on each build (#9072)
When developing the macOS app, we always build the exact same version
and build code for each build. ~~This _may_ be one reason why we
constantly have to deactivate the extension before the new one will
launch.~~ Edit: Just tested, and I can verify that this does fix the
issue on dev builds, so no more having to uninstall the sysex between
builds.

Even if that's not the reason, this is a cleaner approach than building
it in our prod-only scripts.

---------

Signed-off-by: Jamil <jamilbk@users.noreply.github.com>
2025-05-12 00:55:14 +00:00
Jamil
091b52ef07 ci: Prevent having to manually update provisioning profile UUIDs (#9074)
When updating the provisioning profiles (i.e. when changing anything the
Apple Developer Portal), we needed to manually update these build
scripts to point to the new UUIDs.

This can be made simpler to automatically pull it out of the profiles in
CI.
2025-05-11 22:54:14 +00:00
Jamil
07fa91f713 chore(apple): Add file header template for xcode (#9068)
When creating new files in Xcode, it's helpful if the file header is
already formatted properly.
2025-05-10 12:26:37 +00:00
Jamil
3de8a1e405 fix(apple/iOS): Use pointer directly for libresolv API calls (#9038)
Somewhere between Xcode 16.0 and Xcode 16.3, the API for the libresolv
functions we call changed slightly, and we can now pass the return value
of `__res_9_state()` directly to the `res_9_ninit`, `res_9_ndestroy` and
`res_9_getservers` functions.
2025-05-06 03:32:35 +00:00
Jamil
6e0e7343ba chore: release Apple & Gateway with ECN fix (#9013) 2025-05-02 00:16:40 -07:00
Jamil
2650d81444 chore: release clients with GSO fix (#8936) 2025-04-29 23:52:43 -07:00
Thomas Eizinger
2ba7a87899 feat(connlib): add FFI for changing log-level on MacOS (#8927)
This isn't plugged into anything yet on the Swift side but lays the
foundation for changing the log-level at runtime without having to sign
the user out.
2025-04-29 13:51:46 +00:00
Jamil
5db8e20f3b chore: release Apple and GUI clients (#8882)
- Apple clients 1.4.12
- GUI clients 1.4.11
2025-04-21 21:45:16 +00:00
Jamil
5669c83835 ci: Bump Apple clients to 1.4.11 (#8848)
Includes a fix for auto-starting on launch when other VPN clients have
been connected previously.
2025-04-19 11:45:42 +00:00
Jamil
2f43bb9110 chore(apple): Disable Sentry debug mode on dev (#8816)
This just adds a bunch of log noise and is only helpful if we're trying
to debug the Sentry integration itself.
2025-04-18 18:07:53 +00:00
Jamil
22113f5940 chore(apple): Bump Swift dependencies (#8815)
Dependabot still fails to update these...
2025-04-18 18:07:42 +00:00
Jamil
4c1379a6bf fix(apple): Force enable VPN configuration on autoStart (#8814)
If another VPN has been activated on the system while Firezone is
active, Apple OSes will deactivate our configuration, and never
reactivate it.

We knew this already, and always activated the configuration when
starting during the sign in flow, but failed to also do this when
autoStarting on launch.

This PR updates ensures that during autoStart, we re-enable the
configuration as well.

Fixes #8813
2025-04-18 18:00:44 +00:00
Jamil
a2e32a4918 ci: Bump apple to 1.4.10 to ship PKG (#8797)
This publishes the 1.4.10 permalinks for the PKG download.
2025-04-17 15:13:44 +00:00
Jamil
54e60ca820 fix(ci): Use Developer ID Installer cert to sign pkg (#8796)
Apple requires standalone-distributed `PKG` installers to be signed with
a Developer ID Installer certificate.

Fixes
https://github.com/firezone/firezone/actions/runs/14497960810/job/40670440720#step:6:3500

---------

Signed-off-by: Jamil <jamilbk@users.noreply.github.com>
2025-04-16 18:29:59 +00:00
Jamil
aab691a67f ci: Release Apple clients 1.4.9 (#8793)
These contain the recent UDP thread enhancements.
2025-04-15 20:14:43 +00:00
Jamil
effe169414 chore: release apple 1.4.8 (#8499)
Introduces the autoconnect and session end fixes.
2025-03-21 11:43:00 +00:00
Thomas Eizinger
883c38cd3c fix(connlib): remove explicit Session::disconnect (#8474)
Within the event-loop, we already react to the channel being closed
which happens when the `Sender` within the `Session` gets dropped. As
such, there is no need to send an explicit `Stop` command, dropping the
`Session` is equivalent.

As it turns out, `swift-bridge` already calls `Drop` for us when the
last pointer is set to `nil`:
280a9dd999/swift/apple/FirezoneNetworkExtension/Connlib/Generated/connlib-client-apple/connlib-client-apple.swift (L24-L28)

Thus, we can also remove the explicit `disconnect` call to
`WrappedSession` entirely.
2025-03-18 04:35:57 +00:00
Jamil
a8b9e34c33 fix(apple): Try to connect on launch (#8477)
This is a regression introduced in c9f085c102. The `status` at this
point is still `nil` because we have not yet fully subscribed to VPN
status change updates from the system.

That actually shouldn't prevent us from trying to start the tunnel
anyway. If the `token` is missing from the Keychain, the tunnel process
will no-op. So we simply try to start a session on launch always.

Fixes #8456
2025-03-18 03:06:57 +00:00
Jamil
e642eefb35 chore: Cut all clients to ship search domains (#8442)
Waiting on app reviews to be approved, then this PR will be ready to
merge.
2025-03-17 17:25:11 +00:00
Jamil
da73441d6c fix(swift): Allow too many parameters in onSetInterfaceConfig (#8446)
We don't necessarily care if we slightly go over the function parameter
count lint in `onSetInterfaceConfig`.
2025-03-15 18:09:19 -05:00
Thomas Eizinger
58d241f705 feat(apple): pass-through search domain to VPN resolver config (#8421)
In order to have the system expand search domains for us, we need to set
a very peculiar combination of configuration options in the
`NEDNSSettings` of the VPN configuration:

- We need to include our search domains in the list of `matchDomains`
- We need to set `matchDomainsNoSearch = false`
- We need to set the `searchDomains` field

Technically, we don't even need to set `searchDomains` by itself.
Reading the docs in more detail for the `matchDomainsNoSearch` flag
explains why:

> A Boolean that specifies if the domains in the matchDomains list
should not be appended to the resolver’s list of search domains.

The double-negative here is confusing but essentially, what this says
is:

> If false, append the list of match domains to the resolver's search
domains.

That is exactly what we want. We want a search domain of e.g.
`example.com` to append to the list of search domains for the primary
resolver of non-scoped DNS queries.

I tested without setting `searchDomains` and it does still work: The
system will still expand the domain for us und send us a FQDN query of
e.g. `foo.example.com`. However, I figured not setting `searchDomains`
at all is quite confusing so I left it in there.

Related: #8410 (Fixes it for MacOS)

---------

Signed-off-by: Thomas Eizinger <thomas@eizinger.io>
Co-authored-by: thomas <firezone@firezones-MacBook-Air.fritz.box>
Co-authored-by: Jamil <jamilbk@users.noreply.github.com>
2025-03-13 06:08:27 +00:00
Jamil
25c708fb43 ci: Bump apple clients to 1.4.6 (#8418) 2025-03-12 04:09:49 +00:00
Jamil
ab7e805fdd fix(apple): actually show user-friendly alert messages (#8282)
Before, we would receive an `NSError` object and the type-matching
wouldn't take effect at all, causing the default alert to show every
time. This solves that by introducing a `UserFriendlyError` protocol
which is more robust against the two main `Error` and `NSError`
variants.
2025-02-28 14:12:24 +00:00
Jamil
c9f085c102 refactor(apple): Split IPC and VPN config into separate classes (#8279)
The current `VPNConfigurationManager` class is too large and handles 3
separate things:

- Encoding IPC messages
- Sending IPC messages
- Loading/storing the VPN configuration

With this PR, these are split out into:

- ProviderMessage class
- IPCClient class
- VPNConfigurationManager class

These are then use directly from our `Store` in order to load their
state upon `init()`, set the relevant properties, and thus views are
updated accordingly.

A couple minor bugs are fixed as well as part of the refactor.

### Tested: macOS

- [x] Sign in
- [x] Sign out
- [x] Yanking the VPN configuration while signed in / out
- [x] Yanking the system extension while signed in / out
- [x] Denying the VPN configuration
- [x] Denying Notifications
- [x] Denying System Extension

### Tested: iOS

- [x] Sign in
- [x] Sign out
- [x] Yanking the VPN configuration while signed in / out
- [x] Yanking the system extension while signed in / out
- [x] Denying the VPN configuration
- [x] Denying Notifications

---------

Signed-off-by: Jamil <jamilbk@users.noreply.github.com>
2025-02-28 04:35:26 +00:00
Jamil
48030f68d7 ci: Bump Apple clients to 1.4.5 (#8252)
These have been published. This fixes a critical bug preventing the
client from launching on macOS.
2025-02-24 23:41:38 -08:00
Jamil
c9a2917a52 fix(apple): Encode/Decode SemanticVersion (#8251)
`SemanticVersion` needs to be properly encoded / decoded before saving
to UserDefaults.
2025-02-24 12:04:44 -08:00
Jamil
4defd9695d fix(apple): Process update notifications on main thread only (#8248)
`@Published` properties that views subscribe to for UI updates need to
be updated from the main thread only. This PR annotates the relevant
variable and function from the original author's implementation with
`@MainActor` so that Swift will properly warn us when modifying these in
the future.
2025-02-24 10:46:31 -08:00
Jamil
cf837a3507 fix(apple): Pass menuBar to AppView (#8249)
A regression was introduced in #8218 that removed the `menuBar` as an
environment object for `AppView`.

Unfortunately this compiles just fine, as EnvironmentObjects are loaded
at runtime, causing the "Open Menu" button to crash since it's looking
for a non-existent EnvironmentObject.
2025-02-24 10:44:55 -08:00
Jamil
0bc3895c3e ci: Bump Apple clients to 1.4.4 (#8245)
These have been released / published. Need to merge this to get website
links and changelog updated.
2025-02-24 09:01:45 -08:00
Jamil
b9c0ba9c3a fix(apple): web auth session mem leak (#8237)
We had a very small memory leak due to a circular reference in the
`WebAuthSession` class.

<img width="1552" alt="Screenshot 2025-02-23 at 7 00 50 PM"
src="https://github.com/user-attachments/assets/d91a6fb9-8d96-4648-a451-0d1361870b28"
/>
2025-02-24 03:26:56 +00:00
Jamil
d7ed8ac248 refactor(apple): Clean up MenuBar vars, function names (#8234)
The original MenuBar developer set a few anti-patterns that were
somewhat followed by subsequent developers. As of now, the entire file
is too large and woefully cluttered.

This PR takes a big step towards #7771 by first organizing the current
thing into a more comprehensible file:


- `private` is removed. These are not needed in Swift unless you
actually need to make something private. The default `internal` level is
appropriate for most cases.
- state change handlers are consistently named `handleX`
- functions are reorganized, and `MARK` comments used to group similar
functions together
2025-02-24 03:13:15 +00:00
Jamil
83b2c7a71a refactor(apple): Collapse ViewModels to app-wide Store (#8218)
Our application state is incredibly simple, only consisting of a handful
of properties.

Throughout our codebase, we use a singular global state store called
`Store`. We then inject this as the singular piece of state into each
view's model.

This is unnecessary boilerplate and leads to lots of duplicated logic.
Instead we refactor away (nearly) all of the application's view models
and instead use an `@EnvironmentObject` to inject the store into each
view.

This convention drastically simplifies state tracking logic and
boilerplate in the views.
2025-02-24 02:28:29 +00:00
Jamil
31e7aef77a revert: loading NSImage asynchronously (#8215)
Loading images async isn't fixing the App Hanging reports we continue to
receive, so it's something else. Rather than trying to load them
asynchronously, we revert that change.

We instead eager-load all images needed by the MenuBar at init instead
of lazy-loading them, which in rare cases could cause apparent UI hangs.
If we can't load them we log an error but continue to try an operate, as
the icons are not strictly needed for Firezone operation.


Reverts firezone/firezone#8090
2025-02-20 23:41:39 +00:00
Jamil
0ae74fe126 refactor(apple): Don't initialize Favorites twice (#8216)
For some reason, this was being initialized twice, when it doesn't need
to be.

The whole reason Favorites is initialized in the FirezoneApp module is
so we can have one instance of it passed down to children.
2025-02-20 21:27:52 +00:00
Thomas Eizinger
cad84922db fix(apple): don't panic in FFI functions (#8202)
Now that we have error reporting via Sentry in Swift-land as well, we
can handle errors in the FFI layer more gracefully and return them to
Swift.

---------

Signed-off-by: Jamil <jamilbk@users.noreply.github.com>
Co-authored-by: Jamil <jamilbk@users.noreply.github.com>
2025-02-20 00:51:56 +00:00
Jamil
e487272a1b chore(apple): Release Apple clients 1.4.3 (#8144) 2025-02-16 12:59:38 -08:00
Jamil
df8b615d35 fix(apple/macOS): Don't force unwrap for menubar items (#8135)
We can elegantly handle nil items in places where we currently don't.
This PR updates all cases in MenuBar.swift to gracefully handle nil
items like the menubar icons which can, in rare circumstances, be `nil`
if they haven't yet loaded.
2025-02-14 21:50:35 +00:00
Jamil
e23bd97ea1 fix(apple): Persist last notified version (#8122)
Notifications on Apple platforms are delivered with best-effort
reliability and are not guaranteed.

They can also be queued up by the system so that, for example, it's
possible to issue a notification, quit the app, and then upon the next
launch of the app, receive the notification.

In this second case, if the user dismissed the notification, we will
crash. This is because we only track the `lastNotifiedVersion` in the
`NotificationAdapter` instance object and don't persist it to disk, then
we assert the value not to be nil when saving the user's `dismiss`
action.

To fix this, we persist the `lastNotifiedVersion` to the `UserDefaults`
store and attempt to read this when the user is dismissing the
notification. If we can't read it for some reason, we still dismiss the
notification but won't prevent showing it again on the next update
check.

A minor bug is also fixed where the original author didn't correctly
call the function's `completionHandler`. Also, unused instance vars
`lastDismissedVersion` left over from the original author are removed as
well.
2025-02-13 23:57:58 +00:00
Jamil
39cbf60ec8 ci: Bump Apple clients to 1.4.2 (#8109)
Fixes a slew of memory leaks, crashes, and other papercuts.
2025-02-13 22:08:45 +00:00
Jamil
62876028c8 chore(apple): Update Xcode project settings (#8114)
Xcode keeps pestering about these on each launch. Seems to be
maintainence-related project configuration updates.
2025-02-13 02:40:23 +00:00
Jamil
9a3cde89b9 refactor(apple): Don't create variables we don't use (#8115)
Both warnings-as-errors and the linter don't error on this particular
warning unfortunately.

👎
2025-02-13 02:40:12 +00:00
Jamil
cf1b74cdc1 fix(apple): Only use connlib sessions that are connected (#8104)
In the window of time between we check `AdapterState == .tunnelStarted`
and we call `setDns` in the Apple `pathUpdateHandler`, it's possible
that connlib disconnected. This window of time could potentially be
non-trivial since we read system resolvers in there, which hits the
disk.

As such, we should always check the `session` pointer is valid just
before use.

The `AdapterState` enum tracks two states: `tunnelStopped` and
`tunnelStarted`. In the `tunnelStarted` state, we populate a
`WrappedSession` object. This is redundant - connlib is either
`connected` and we have a `WrappedSession`, or it is not. Therefore we
can remove the `AdapterState` abstraction completely (which was leftover
from a previous developer) and directly use a `WrappedSession?` object
to issue calls to connlib with.

We set this to a valid `WrappedSession` upon connecting, and back to
`nil` as soon as connlib either `onDisconnect`s us, or the user
disconnects the tunnel.

Lastly, we avoid early-returning from queued workItems because we now
call connlib with `session?` which will no-op if there is no session,
allowing whatever IPC call running at the time (such as fetchResources)
to complete successfully, even though they'll see a "snapshotted" state
of the Adapter/PacketTunnelProvider. In other words, we no longer
enforce the session pointer to be valid for things that don't depend on
its state.

Fixes #7882
2025-02-12 19:31:39 +00:00
Jamil
9f88cd16f4 fix(apple): Load NSImage in MenuBar asynchronously (#8090)
After further investigation, it appears that the `NSImage` initializer
loads and decodes images *synchronously* from the disk. In the MenuBar,
we are "lazy-loading" these images, but since the menu is constructed as
part of app initialization, we are effectively loading these when the
app boots, in `FirezoneApp`.

After loading, these are cached, but the initial can hang the UI thread
on app launch for slow systems.

Unfortunately, `NSImage` does not _formally_ conform to `@Sendable`.
However, this may be a nuance that isn't true in most cases, such as
when treating `NSImage` instances as read-only from only a single
thread.

As such, we wrap `NSImage` with our own struct, and mark it `@unchecked
Sendable`. This allows us to load the images on a background thread and
assign them to their UI thread counterparts in an async manner.

See further discussion:

-
https://forums.swift.org/t/why-cant-i-send-an-nsimage-across-actor-boundaries/76199
-
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-126728


Related: #7771
2025-02-11 14:36:40 +00:00
Jamil
41f4ae5e7f fix(apple/macOS): Move to .idle state after log export (#8091)
This fixes a bug where we couldn't export logs twice because we never
returned to the `.idle` state after export.

Fixes #8015
2025-02-11 07:07:27 +00:00
Jamil
063dc73d01 refactor(apple): Remove useless Task.detached (#8063)
Whether we execute a task on the main thread or a background thread
doesn't affect whether the thread is "hung" as reported by Sentry.

Instead, our options for fixing these are:

- Try to use an async version of the underlying API (the [async
version](https://developer.apple.com/documentation/appkit/nsworkspace/open(_:configuration:completionhandler:))
of `open` for example)
- If there is none, and the call could potentially block (most likely to
do disk IO contention), at least schedule this on a new thread using
`Task.detached` but with `.background` priority so that it will avoid
blocking any other execution.

The main takeaway here is that unfortunately, under some conditions,
Sentry will _always_ report an "App Hanging" alert since it's constantly
monitoring all threads for paused execution longer than 2000ms.

We'll probably end up letting some of these slide (pausing a background
or worker thread isn't necessarily a UX issue), but pausing the UI
thread is.

Luckily, we're able to use async APIs for most things. The remaining
things (like working with log files over IPC) we use a `Task.detached`
for.
2025-02-11 04:55:55 +00:00
Jamil
e8384ea5b0 refactor(apple): Make IPC calls async, bubbling errors (#8062)
`fetchResources` is an IPC call, and we can use
`withCheckedThrowingContinuation` like the others to yield while we wait
for the provider to respond.

The particular sentry issue related to this isn't because we are
necessarily blocking the task thread, rather, I suspect it's when
applying the fetched Resources to the UI that we're slow. There isn't
much we can do about this, but this PR will only help.

Because we're using a timer that fires off a closure to do this, we
still use a `callback` inside the timer to actually set the Resources on
the main `Store`, which updates the UI.

Unfortunately refactoring these IPC calls lead to somewhat of a ball of
yarn, so the best way to summarize the spirit of this PR is:

- Ensure IPC calls use `withCheckedThrowingContinuation` where possible
- Thusly, marking these functions `async throws`
- Bubble these errors up the view where we can ultimately decide what to
do with them
- Keep VPN state management and conditional logic based on `NEVPNStatus`
in the vpnConfigurationManager
2025-02-10 22:38:05 +00:00
Jamil
0e990d29d3 refactor(apple): Annotate update resources callback @MainActor (#8069)
Followup to the discussion on
https://github.com/firezone/firezone/pull/8064. By annotating the
callback that updates our Resources `@MainActor`, the compiler will
correctly warn us when we call it from a non-isolated context.
2025-02-10 03:53:22 +00:00
Jamil
36f5eee99d fix(apple): Prevent dupe resources timer scheduling (#8066)
If we receive two `.connected` status change updates from the system in
a row, we'll incorrectly schedule an additional timer, and the handle to
the first one will be lost.

This causes a memory leak because we'll then never call the first
timer's `invalidate()` function in the `endUpdatingResources` call.
2025-02-09 23:12:25 +00:00
Jamil
ce81dc5e98 fix(apple): Ensure resources are updated on MainActor (#8064)
This fixes a bug introduced in db655dd171 that could lead to a crash or
undefined behavior because it potentially updates resources (a
`@Published` object) from a background thread.

UI updates must occur on the main thread only.
2025-02-09 22:40:53 +00:00
Jamil
7dc6e36fb0 Revert 'fix(apple): Make Favorites load/save async' (#8068)
Turns out I was wrong about UserDefaults - they're persisted, but backed
by an in-memory cache. The persisting is handled asynchronously, so this
is unlikely to be the culprit of
https://firezone-inc.sentry.io/issues/6225229650/?project=4508175177023488&query=is%3Aunresolved%20issue.priority%3A%5Bhigh%2C%20medium%5D&referrer=issue-stream&statsPeriod=30d&stream_index=1

More likely it has to do with some other synchronous I/O, so the search
continues. Unfortunately we don't have great backtraces for this issue
because it looks like the hang is happening outside the scope of our
code, and seems to be macOS-only in low memory conditions.

Reverts firezone/firezone#8060
2025-02-09 14:52:31 -08:00