Compare commits

...

10 commits

Author SHA1 Message Date
Caleb Jasik
0054a8778c
Merge 281d690ef3 into 5b1724d454 2025-06-18 07:56:54 -07:00
Ian VanSchooten
5b1724d454
Update mobile_scanner dependency (#282) 2025-06-12 13:25:21 -04: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
11 changed files with 73 additions and 52 deletions

View file

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

View file

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

View file

@ -10,11 +10,11 @@ PODS:
- path_provider_foundation (0.0.1): - path_provider_foundation (0.0.1):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
- Sentry/HybridSDK (8.42.0) - Sentry/HybridSDK (8.46.0)
- sentry_flutter (8.12.0): - sentry_flutter (8.14.2):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
- Sentry/HybridSDK (= 8.42.0) - Sentry/HybridSDK (= 8.46.0)
- share_plus (0.0.1): - share_plus (0.0.1):
- Flutter - Flutter
- SwiftyJSON (5.0.2) - SwiftyJSON (5.0.2)
@ -56,16 +56,16 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/url_launcher_ios/ios" :path: ".symlinks/plugins/url_launcher_ios/ios"
SPEC CHECKSUMS: SPEC CHECKSUMS:
file_picker: 8272ff2f2365937598e2407f4f2ff55c723f084a file_picker: c79185e70b9b45728cde2a8d8da454e0cb43f287
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93 mobile_scanner: 77265f3dc8d580810e91849d4a0811a90467ed5e
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
Sentry: 38ed8bf38eab5812787274bf591e528074c19e02 Sentry: da60d980b197a46db0b35ea12cb8f39af48d8854
sentry_flutter: a72ca0eb6e78335db7c4ddcddd1b9f6c8ed5b764 sentry_flutter: 2df8b0aab7e4aba81261c230cbea31c82a62dd1b
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
SwiftyJSON: f5b1bf1cd8dd53cd25887ac0eabcfd92301c6a5a SwiftyJSON: f5b1bf1cd8dd53cd25887ac0eabcfd92301c6a5a
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
PODFILE CHECKSUM: b44d9de9944d89118a4ff4bfffe1c2dab91de156 PODFILE CHECKSUM: b44d9de9944d89118a4ff4bfffe1c2dab91de156

View file

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

View file

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

View file

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

View file

@ -1,12 +1,12 @@
import Foundation import Foundation
import os.log import os.log
class DNUpdater { class DNUpdater: @unchecked Sendable {
private let apiClient = APIClient() private let apiClient = APIClient()
private let timer = RepeatingTimer(timeInterval: 15 * 60) // 15 * 60 is 15 minutes private let timer = RepeatingTimer(timeInterval: 15 * 60) // 15 * 60 is 15 minutes
private let log = Logger(subsystem: "net.defined.mobileNebula", category: "DNUpdater") 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 _ = SiteList { (sites, _) -> Void in
// NEVPN seems to force us onto the main thread and we are about to make network calls that // 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. // 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 = { timer.eventHandler = {
self.updateAll(onUpdate: onUpdate) self.updateAll(onUpdate: onUpdate)
} }
timer.resume() 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 = { timer.eventHandler = {
self.updateSite(site: site, onUpdate: onUpdate) self.updateSite(site: site, onUpdate: onUpdate)
} }
timer.resume() timer.resume()
} }
func updateSite(site: Site, onUpdate: @escaping (Site) -> Void) { func updateSite(site: Site, onUpdate: @Sendable @escaping (Site) -> Void) {
do { do {
if !site.managed { if !site.managed {
return 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 eventSink: FlutterEventSink?
private var eventChannel: FlutterEventChannel private var eventChannel: FlutterEventChannel
private var site: Site private var site: Site
private var notification: Any? private var notification: Any?
public var startFunc: (() -> Void)? public var startFunc: (() -> Void)?
private var configFd: Int32? = nil private var configFd: Int32?
private var configObserver: (any DispatchSourceFileSystemObject)? = nil private var configObserver: (any DispatchSourceFileSystemObject)?
init(messenger: any FlutterBinaryMessenger, site: Site) { init(messenger: any FlutterBinaryMessenger, site: Site) {
do { do {

View file

@ -152,6 +152,10 @@ class SwitchCameraButton extends StatelessWidget {
icon = const Icon(Icons.camera_front); icon = const Icon(Icons.camera_front);
case CameraFacing.back: case CameraFacing.back:
icon = const Icon(Icons.camera_rear); icon = const Icon(Icons.camera_rear);
case CameraFacing.external:
icon = const Icon(Icons.usb);
case CameraFacing.unknown:
icon = const Icon(Icons.device_unknown);
} }
return IconButton( return IconButton(

View file

@ -316,10 +316,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: mobile_scanner name: mobile_scanner
sha256: "1779bf862cfcf7a142117e707e2230624d42f153ddf51f4cc9f5ba455a2dd01e" sha256: "54005bdea7052d792d35b4fef0f84ec5ddc3a844b250ecd48dc192fb9b4ebc95"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.0.0-beta.4" version: "7.0.1"
package_info_plus: package_info_plus:
dependency: "direct main" dependency: "direct main"
description: description:
@ -452,26 +452,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: sentry name: sentry
sha256: "576ad83415102ba2060142a6701611abc6e67a55af1d7ab339cedd3ba1b0f84c" sha256: "599701ca0693a74da361bc780b0752e1abc98226cf5095f6b069648116c896bb"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.12.0" version: "8.14.2"
sentry_dart_plugin: sentry_dart_plugin:
dependency: "direct main" dependency: "direct main"
description: description:
name: sentry_dart_plugin name: sentry_dart_plugin
sha256: "14c298de1be3ba3a6a16d9ce0aad8662b14ca6ed85b8ade234f75b2f3c285edf" sha256: "84436958fa9231e2e716be117a3b31695e54458b9f27039f76d14515e24248a6"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.1" version: "2.4.1"
sentry_flutter: sentry_flutter:
dependency: "direct main" dependency: "direct main"
description: description:
name: sentry_flutter name: sentry_flutter
sha256: dc3761e8659839cc67a18432d9f12e5531affb7ff68e196dbb56846909b5dfdc sha256: "5ba2cf40646a77d113b37a07bd69f61bb3ec8a73cbabe5537b05a7c89d2656f8"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.12.0" version: "8.14.2"
share_plus: share_plus:
dependency: "direct main" dependency: "direct main"
description: description:
@ -719,4 +719,4 @@ packages:
version: "3.1.3" version: "3.1.3"
sdks: sdks:
dart: ">=3.7.0 <4.0.0" dart: ">=3.7.0 <4.0.0"
flutter: ">=3.27.0" flutter: ">=3.29.0"

View file

@ -36,9 +36,9 @@ dependencies:
flutter_svg: ^2.0.10+1 flutter_svg: ^2.0.10+1
intl: ^0.19.0 intl: ^0.19.0
share_plus: ^10.0.2 share_plus: ^10.0.2
sentry_flutter: ^8.9.0 sentry_flutter: ^8.14.2
sentry_dart_plugin: ^2.0.0 sentry_dart_plugin: ^2.4.1
mobile_scanner: ^7.0.0-beta.3 mobile_scanner: ^7.0.1
path: ^1.9.1 path: ^1.9.1
dev_dependencies: dev_dependencies: