mobile_nebula/ios/Runner/DNUpdate.swift
Caleb Jasik 330c8348fb
Enable Swift 6 feature flags (#258)
* Specify Swift Language Version 5

* Enable `GlobalConcurrency` swift feature flag

<945d93776c/proposals/0412-strict-concurrency-for-global-variables.md>

* Enable `InferSendableFromCaptures` swift feature flag 
<945d93776c/proposals/0418-inferring-sendable-for-methods.md (L4)>

* Enable `IsolatedDefaultValues` swift feature

<945d93776c/proposals/0411-isolated-default-values.md>

* Enable `ImplicitOpenExistentials` swift feature flag

<https://github.com/swiftlang/swift-evolution/blob/main/proposals/0352-implicit-open-existentials.md>

* Enable `DeprecateApplicationMain` swift feature flag

<945d93776c/proposals/0383-deprecate-uiapplicationmain-and-nsapplicationmain.md>

* Enable `ForwardTrailingClosures` swift feature flag

<945d93776c/proposals/0286-forward-scan-trailing-closures.md>

* Enable `ConciseMagicFile` swift feature flag

<945d93776c/proposals/0274-magic-file.md>

* Enable `ImportOBJCForwardDeclarations` swift feature flag

<945d93776c/proposals/0384-importing-forward-declared-objc-interfaces-and-protocols.md>

* Enable `DisableOutwardActorInference` swift feature flag

<https://github.com/swiftlang/swift-evolution/blob/main/proposals/0401-remove-property-wrapper-isolation.md>

* Enable `ExistentialAny` swift feature flag

<945d93776c/proposals/0335-existential-any.md (L4)>

* Annotate existentials with `any` prefix keyword

<https://github.com/swiftlang/swift-evolution/blob/main/proposals/0335-existential-any.md>

* Enable `RegionBasedIsolation` swift feature flag

This adds two warnings to the build complaining about a sending closure.

<945d93776c/proposals/0414-region-based-isolation.md>
2025-02-25 10:35:12 -06:00

147 lines
3.9 KiB
Swift

import Foundation
import os.log
class DNUpdater {
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) {
_ = 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.
Task.detached(priority: .userInitiated) {
sites?.values.forEach { site in
if site.connected == true {
// The vpn service is in charge of updating the currently connected site
return
}
self.updateSite(site: site, onUpdate: onUpdate)
}
}
}
}
func updateAllLoop(onUpdate: @escaping (Site) -> Void) {
timer.eventHandler = {
self.updateAll(onUpdate: onUpdate)
}
timer.resume()
}
func updateSingleLoop(site: Site, onUpdate: @escaping (Site) -> Void) {
timer.eventHandler = {
self.updateSite(site: site, onUpdate: onUpdate)
}
timer.resume()
}
func updateSite(site: Site, onUpdate: @escaping (Site) -> Void) {
do {
if !site.managed {
return
}
let credentials = try site.getDNCredentials()
let newSite: IncomingSite?
do {
newSite = try apiClient.tryUpdate(
siteName: site.name,
hostID: credentials.hostID,
privateKey: credentials.privateKey,
counter: credentials.counter,
trustedKeys: credentials.trustedKeys
)
} catch (APIClientError.invalidCredentials) {
if !credentials.invalid {
try site.invalidateDNCredentials()
log.notice("Invalidated credentials in site: \(site.name, privacy: .public)")
}
return
}
let siteManager = site.manager
let shouldSaveToManager =
siteManager != nil
|| ProcessInfo().isOperatingSystemAtLeast(
OperatingSystemVersion(majorVersion: 17, minorVersion: 0, patchVersion: 0))
newSite?.save(manager: site.manager, saveToManager: shouldSaveToManager) { error in
if error != nil {
self.log.error("failed to save update: \(error!.localizedDescription, privacy: .public)")
}
// reload nebula even if we couldn't save the vpn profile
onUpdate(Site(incoming: newSite!))
}
if credentials.invalid {
try site.validateDNCredentials()
log.notice("Revalidated credentials in site \(site.name, privacy: .public)")
}
} catch {
log.error(
"Error while updating \(site.name, privacy: .public): \(error.localizedDescription, privacy: .public)"
)
}
}
}
// From https://medium.com/over-engineering/a-background-repeating-timer-in-swift-412cecfd2ef9
class RepeatingTimer {
let timeInterval: TimeInterval
init(timeInterval: TimeInterval) {
self.timeInterval = timeInterval
}
private lazy var timer: any DispatchSourceTimer = {
let t = DispatchSource.makeTimerSource()
t.schedule(deadline: .now(), repeating: self.timeInterval)
t.setEventHandler(handler: { [weak self] in
self?.eventHandler?()
})
return t
}()
var eventHandler: (() -> Void)?
private enum State {
case suspended
case resumed
}
private var state: State = .suspended
deinit {
timer.setEventHandler {}
timer.cancel()
/*
If the timer is suspended, calling cancel without resuming
triggers a crash. This is documented here https://forums.developer.apple.com/thread/15902
*/
resume()
eventHandler = nil
}
func resume() {
if state == .resumed {
return
}
state = .resumed
timer.resume()
}
func suspend() {
if state == .suspended {
return
}
state = .suspended
timer.suspend()
}
}