summaryrefslogtreecommitdiff
path: root/octosentry/UpdateChecker.swift
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-07-17 17:34:22 -0500
committerChristian Cleberg <[email protected]>2026-07-17 17:34:57 -0500
commit705c6029ab30adf094e6324006b9fb682d8189f2 (patch)
treea7325e1d8900a4c7c5b05182cd95dbf6b7f374ce /octosentry/UpdateChecker.swift
parent29b01b0220968dd9ea70b6c84ca72603bc02042c (diff)
downloadoctosentry-0.7.0.tar.gz
octosentry-0.7.0.tar.bz2
octosentry-0.7.0.zip
Add repo picker, update checker, and distribution tooling1.0.00.7.00.6.0
Closes #11-#15 (milestones 0.6.0, 0.7.0, 1.0.0). - Repo picker: on-demand broader OAuth scope (security_events repo), requested only when the "Browse your repos" action is used, never by default. Lists /user/repos via the existing pagination helper. Granted scope persisted with backward-compatible decoding for existing state files. Fixed a bug where a failed re-auth force-signed-out a user who already had a valid narrower-scope token. - Update checker: polls this repo's GitHub Releases API, surfaces a banner linking to new releases. Skipped on the Mac App Store build via a runtime receipt check rather than a separate build configuration. - Fixed MARKETING_VERSION, stuck at Xcode's default "1.0" this whole time unrelated to our git tags โ€” now 1.0.0, matching this release. - Added PrivacyInfo.xcprivacy (no tracking, no collected data). - Added scripts/build-dmg.sh (archive, Developer ID export, notarize, staple) and Casks/octosentry.rb (Homebrew Cask template), plus DISTRIBUTION.md documenting both channels end to end. Entitlements were already identical across all builds โ€” no divergence needed there. What remains for actual App Store submission and notarized DMG builds is account-specific (Apple Developer Program membership, certificates, App Store Connect submission) and can't be done from here; documented clearly in DISTRIBUTION.md.
Diffstat (limited to 'octosentry/UpdateChecker.swift')
-rw-r--r--octosentry/UpdateChecker.swift85
1 files changed, 85 insertions, 0 deletions
diff --git a/octosentry/UpdateChecker.swift b/octosentry/UpdateChecker.swift
new file mode 100644
index 0000000..e90cab1
--- /dev/null
+++ b/octosentry/UpdateChecker.swift
@@ -0,0 +1,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 = "zerolabsco"
+ 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)"
+ }
+ }
+}