mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-17 01:13:19 +08:00
79 lines
3.1 KiB
Swift
79 lines
3.1 KiB
Swift
import Foundation
|
|
import Combine
|
|
|
|
/// Main-thread local-app checks triggered by visits, session endings and manual requests.
|
|
final class AppUpdateChecker: ObservableObject {
|
|
enum Status { case idle, checking, upToDate, available, failed }
|
|
// Fetchers complete asynchronously on the main thread.
|
|
typealias Fetch = (@escaping (Data?) -> Void) -> (() -> Void)
|
|
@Published private(set) var status: Status = .idle
|
|
@Published private(set) var availableVersion = ""
|
|
var updateAvailable: Bool { !availableVersion.isEmpty }
|
|
let currentVersion: String
|
|
|
|
private let fetch: Fetch
|
|
private let compare: (String, Data) -> String?
|
|
private var enabled = false
|
|
private var generation = UUID()
|
|
private var cancelRequest: (() -> Void)?
|
|
|
|
init(currentVersion: String = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "",
|
|
fetch: @escaping Fetch = AppUpdateChecker.fetchRelease,
|
|
compare: @escaping (String, Data) -> String? = CrossDeskRTCBridge.checkMobileUpdate) {
|
|
self.currentVersion = currentVersion
|
|
self.fetch = fetch
|
|
self.compare = compare
|
|
}
|
|
|
|
deinit {
|
|
cancelRequest?()
|
|
}
|
|
|
|
func setEnabled(_ value: Bool) {
|
|
guard enabled != value else { return }
|
|
enabled = value
|
|
if enabled { checkNow(); return }
|
|
generation = UUID()
|
|
cancelRequest?()
|
|
cancelRequest = nil
|
|
if status == .checking {
|
|
status = updateAvailable ? .available : .idle
|
|
}
|
|
}
|
|
|
|
func checkNow() {
|
|
guard enabled, status != .checking else { return }
|
|
status = .checking
|
|
let token = UUID()
|
|
generation = token
|
|
cancelRequest = fetch { [weak self] data in
|
|
guard let self, self.enabled, self.generation == token else { return }
|
|
self.cancelRequest = nil
|
|
if let data, let result = self.compare(self.currentVersion, data) {
|
|
self.availableVersion = result
|
|
self.status = self.updateAvailable ? .available : .upToDate
|
|
} else {
|
|
self.status = .failed
|
|
}
|
|
}
|
|
}
|
|
|
|
// Public metadata only: no identity, installed version or credentials sent.
|
|
private static func fetchRelease(completion: @escaping (Data?) -> Void) -> (() -> Void) {
|
|
let configuration = URLSessionConfiguration.ephemeral
|
|
configuration.timeoutIntervalForRequest = 10
|
|
configuration.timeoutIntervalForResource = 15
|
|
let session = URLSession(configuration: configuration)
|
|
let request = URLRequest(url: URL(string: "https://version.crossdesk.cn/version.json")!,
|
|
cachePolicy: .reloadIgnoringLocalCacheData)
|
|
let task = session.dataTask(with: request) { data, response, error in
|
|
let valid = error == nil && (response as? HTTPURLResponse)?.statusCode == 200 &&
|
|
(data?.count ?? 0) <= 256 * 1024
|
|
DispatchQueue.main.async { completion(valid ? data : nil) }
|
|
session.finishTasksAndInvalidate()
|
|
}
|
|
task.resume()
|
|
return { task.cancel(); session.invalidateAndCancel() }
|
|
}
|
|
}
|