Compare commits

...

10 commits

Author SHA1 Message Date
Caleb Jasik
d32d9ccd7f
Merge 281d690ef3 into a8eee16e24 2025-06-25 14:17:25 -05:00
Caleb Jasik
a8eee16e24
Increase the commit fetch depth for releases for sentry's use (#285) 2025-06-24 11:43:05 -05:00
Caleb Jasik
281d690ef3
Use Swift 6 2025-03-27 12:35:12 -05:00
Caleb Jasik
a32d17705c
Use an AsyncStream to avoid sending @MainActor restricted AppDelegate into other isolation domains 2025-03-27 12:35:12 -05:00
Caleb Jasik
df4c3a51b8
Mark closures in DNUpdate as @Sendable 2025-03-27 12:35:12 -05:00
Caleb Jasik
8e9b5fcc4a
Mark PacketTunnelProvider as @unchecked Sendable 2025-03-27 12:35:12 -05:00
Caleb Jasik
f3882997be
Convert dict to Sendable string before throwing 2025-03-27 12:35:12 -05:00
Caleb Jasik
bc67c06ef7
Mark SiteUpdater as @unchecked Sendable 2025-03-27 12:35:12 -05:00
Caleb Jasik
500e49edc3
Mark Flutter import as @preconcurrency 2025-03-27 12:35:12 -05:00
Caleb Jasik
ae34c59456
Enable StrictConcurrency=complete swift feature flag
<https://github.com/swiftlang/swift-evolution/blob/main/proposals/0337-support-incremental-migration-to-concurrency-checking.md>

This causes several warnings. I'm going to start by trying to silence them by reflecting the current state of the project, rather than fixing them directly.
2025-03-27 12:35:11 -05:00
8 changed files with 52 additions and 35 deletions

View file

@ -3,7 +3,7 @@ on:
push:
tags:
# Only builds for tags with a meaningless build number suffix: v1.0.0-1
- 'v[0-9]+.[0-9]+.[0-9]+-*'
- "v[0-9]+.[0-9]+.[0-9]+-*"
jobs:
build:
@ -15,23 +15,23 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #4.2.2
with:
show-progress: false
fetch-depth: 25 # For sentry releases
fetch-depth: 75 # For sentry releases
- name: Set up Go 1.22
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 #5.3.0
with:
go-version: '1.22'
go-version: "1.22"
cache-dependency-path: nebula/go.sum
- uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 #v4.7.0
with:
distribution: 'zulu'
java-version: '17'
distribution: "zulu"
java-version: "17"
- name: Install flutter
uses: subosito/flutter-action@f2c4f6686ca8e8d6e6d0f28410eeef506ed66aff #v2.18.0
with:
flutter-version: '3.29.2'
flutter-version: "3.29.2"
- name: Setup bundletool for APK generation
uses: amyu/setup-bundletool@f7a6fdd8e04bb23d2fdf3c2f60c9257a6298a40a

View file

@ -23,7 +23,7 @@ extension AppMessageError: LocalizedError {
}
}
class PacketTunnelProvider: NEPacketTunnelProvider {
class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
private var networkMonitor: NWPathMonitor?
private var site: Site?

View file

@ -6,7 +6,7 @@ import os.log
let log = Logger(subsystem: "net.defined.mobileNebula", category: "Site")
enum SiteError: Error {
case nonConforming(site: [String: Any]?)
case nonConforming(site: String)
case noCertificate
case keyLoad
case keySave
@ -22,7 +22,7 @@ extension SiteError: CustomStringConvertible {
public var description: String {
switch self {
case .nonConforming(let site):
return String("Non-conforming site \(String(describing: site))")
return String("Non-conforming site \(site)")
case .noCertificate:
return "No certificate found"
case .keyLoad:
@ -150,7 +150,7 @@ let statusString: [NEVPNStatus: String] = [
]
// Represents a site that was pulled out of the system configuration
class Site: Codable {
class Site: Codable, @unchecked Sendable {
// Stored in manager
var name: String
var id: String
@ -208,7 +208,7 @@ class Site: Codable {
let id = dict?["id"] as? String ?? nil
if id == nil {
throw SiteError.nonConforming(site: dict)
throw SiteError.nonConforming(site: String(describing: dict))
}
try self.init(path: SiteList.getSiteConfigFile(id: id!, createDir: false))

View file

@ -563,6 +563,7 @@
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_UPCOMING_FEATURE_CONCISE_MAGIC_FILE = YES;
SWIFT_UPCOMING_FEATURE_DEPRECATE_APPLICATION_MAIN = YES;
SWIFT_UPCOMING_FEATURE_DISABLE_OUTWARD_ACTOR_ISOLATION = YES;
@ -574,7 +575,7 @@
SWIFT_UPCOMING_FEATURE_INFER_SENDABLE_FROM_CAPTURES = YES;
SWIFT_UPCOMING_FEATURE_ISOLATED_DEFAULT_VALUES = YES;
SWIFT_UPCOMING_FEATURE_REGION_BASED_ISOLATION = YES;
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
@ -784,6 +785,7 @@
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_UPCOMING_FEATURE_CONCISE_MAGIC_FILE = YES;
SWIFT_UPCOMING_FEATURE_DEPRECATE_APPLICATION_MAIN = YES;
SWIFT_UPCOMING_FEATURE_DISABLE_OUTWARD_ACTOR_ISOLATION = YES;
@ -795,7 +797,7 @@
SWIFT_UPCOMING_FEATURE_INFER_SENDABLE_FROM_CAPTURES = YES;
SWIFT_UPCOMING_FEATURE_ISOLATED_DEFAULT_VALUES = YES;
SWIFT_UPCOMING_FEATURE_REGION_BASED_ISOLATION = YES;
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
@ -849,6 +851,7 @@
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_UPCOMING_FEATURE_CONCISE_MAGIC_FILE = YES;
SWIFT_UPCOMING_FEATURE_DEPRECATE_APPLICATION_MAIN = YES;
SWIFT_UPCOMING_FEATURE_DISABLE_OUTWARD_ACTOR_ISOLATION = YES;
@ -860,7 +863,7 @@
SWIFT_UPCOMING_FEATURE_INFER_SENDABLE_FROM_CAPTURES = YES;
SWIFT_UPCOMING_FEATURE_ISOLATED_DEFAULT_VALUES = YES;
SWIFT_UPCOMING_FEATURE_REGION_BASED_ISOLATION = YES;
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};

View file

@ -1,10 +1,10 @@
import MobileNebula
@preconcurrency import MobileNebula
enum APIClientError: Error {
case invalidCredentials
}
class APIClient {
struct APIClient: Sendable {
let apiClient: MobileNebulaAPIClient
let json = JSONDecoder()

View file

@ -1,4 +1,4 @@
import Flutter
@preconcurrency import Flutter
import MobileNebula
import NetworkExtension
import SwiftyJSON
@ -25,17 +25,19 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
dnUpdater.updateAllLoop { site in
// Signal the site has changed in case the current site details screen is active
let container = self.sites?.getContainer(id: site.id)
if container != nil {
// Update references to the site with the new site config
container!.site = site
container!.updater.update(connected: site.connected ?? false, replaceSite: site)
}
Task {
for await site in dnUpdater.siteUpdates {
// Signal the site has changed in case the current site details screen is active
let container = self.sites?.getContainer(id: site.id)
if container != nil {
// Update references to the site with the new site config
container!.site = site
container!.updater.update(connected: site.connected ?? false, replaceSite: site)
}
// Signal to the main screen to reload
self.ui?.invokeMethod("refreshSites", arguments: nil)
// Signal to the main screen to reload
self.ui?.invokeMethod("refreshSites", arguments: nil)
}
}
guard let controller = window?.rootViewController as? FlutterViewController else {

View file

@ -1,12 +1,12 @@
import Foundation
import os.log
class DNUpdater {
class DNUpdater: @unchecked Sendable {
private let apiClient = APIClient()
private let timer = RepeatingTimer(timeInterval: 15 * 60) // 15 * 60 is 15 minutes
private let log = Logger(subsystem: "net.defined.mobileNebula", category: "DNUpdater")
func updateAll(onUpdate: @escaping (Site) -> Void) {
func updateAll(onUpdate: @Sendable @escaping (Site) -> Void) {
_ = SiteList { (sites, _) -> Void in
// NEVPN seems to force us onto the main thread and we are about to make network calls that
// could block for a while. Push ourselves onto another thread to avoid blocking the UI.
@ -23,21 +23,33 @@ class DNUpdater {
}
}
func updateAllLoop(onUpdate: @escaping (Site) -> Void) {
func updateAllLoop(onUpdate: @Sendable @escaping (Site) -> Void) {
timer.eventHandler = {
self.updateAll(onUpdate: onUpdate)
}
timer.resume()
}
func updateSingleLoop(site: Site, onUpdate: @escaping (Site) -> Void) {
// Site updates provides an async/await alternative to `.updateAllLoop` that doesn't require a sendable closure.
// https://developer.apple.com/documentation/swift/asyncstream
var siteUpdates: AsyncStream<Site> {
AsyncStream { continuation in
self.updateAllLoop(onUpdate: { site in
continuation.yield(site)
})
}
}
func updateSingleLoop(site: Site, onUpdate: @Sendable @escaping (Site) -> Void) {
timer.eventHandler = {
self.updateSite(site: site, onUpdate: onUpdate)
}
timer.resume()
}
func updateSite(site: Site, onUpdate: @escaping (Site) -> Void) {
func updateSite(site: Site, onUpdate: @Sendable @escaping (Site) -> Void) {
do {
if !site.managed {
return

View file

@ -78,14 +78,14 @@ class Sites {
}
}
class SiteUpdater: NSObject, FlutterStreamHandler {
class SiteUpdater: NSObject, FlutterStreamHandler, @unchecked Sendable {
private var eventSink: FlutterEventSink?
private var eventChannel: FlutterEventChannel
private var site: Site
private var notification: Any?
public var startFunc: (() -> Void)?
private var configFd: Int32? = nil
private var configObserver: (any DispatchSourceFileSystemObject)? = nil
private var configFd: Int32?
private var configObserver: (any DispatchSourceFileSystemObject)?
init(messenger: any FlutterBinaryMessenger, site: Site) {
do {