blob: fe456c78fd8f350400f3a42aea034f98ce1e00a5 (
plain) (
blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
|
//
// UpdateChecker.swift
// octosentry
//
// Polls this repo's own GitHub Releases API (spec §9) — no auto-install,
// no Sparkle, just a link to the release page. Skipped entirely on the
// Mac App Store build, detected at runtime via the presence of an App
// Store receipt rather than a separate build configuration: same
// outcome (this code never runs there) with far less project surface
// than maintaining a second Xcode configuration/scheme just for this.
//
import Foundation
actor UpdateChecker {
private let session: URLSession
private let repoOwner = "krazywarez"
private let repoName = "octosentry"
init(session: URLSession = .shared) {
self.session = session
}
struct LatestRelease: Sendable {
let version: String
let htmlURL: URL
}
func fetchLatestRelease() async throws -> LatestRelease {
var request = URLRequest(url: URL(string: "https://api.github.com/repos/\(repoOwner)/\(repoName)/releases/latest")!)
request.setValue("application/vnd.github+json", forHTTPHeaderField: "Accept")
request.setValue("2022-11-28", forHTTPHeaderField: "X-GitHub-Api-Version")
let data: Data
let response: URLResponse
do {
(data, response) = try await session.data(for: request)
} catch {
throw UpdateCheckError.network(error.localizedDescription)
}
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
throw UpdateCheckError.requestFailed
}
let dto: GitHubReleaseDTO
do {
dto = try JSONDecoder().decode(GitHubReleaseDTO.self, from: data)
} catch {
throw UpdateCheckError.decodingFailed(error.localizedDescription)
}
guard let url = URL(string: dto.htmlUrl) else {
throw UpdateCheckError.decodingFailed("Malformed release URL.")
}
return LatestRelease(version: dto.tagName, htmlURL: url)
}
}
nonisolated struct GitHubReleaseDTO: Decodable {
let tagName: String
let htmlUrl: String
enum CodingKeys: String, CodingKey {
case tagName = "tag_name"
case htmlUrl = "html_url"
}
}
nonisolated enum UpdateCheckError: Error, LocalizedError {
case network(String)
case requestFailed
case decodingFailed(String)
var errorDescription: String? {
switch self {
case .network(let message):
"Network error checking for updates: \(message)"
case .requestFailed:
"Failed to check for updates."
case .decodingFailed(let message):
"Unexpected response checking for updates: \(message)"
}
}
}
|