How vorssaint-utils Handles Update Checks: GitHub Releases API Integration Explained
vorssaint-utils queries the GitHub Releases API every 60 minutes through UpdateService.swift, comparing semantic versions locally and surfacing available DMG updates to users via a state-driven notification system.
The vorssaint-utils repository implements a comprehensive self-update mechanism that automatically monitors GitHub Releases for newer versions while respecting user preferences for stability and frequency. At the heart of this system lies a Swift-based service architecture that balances timely notifications with API efficiency.
Core Update Service Architecture
The update-check logic is centralized in Sources/Vorssaint/Services/Update/UpdateService.swift, which orchestrates network requests, state management, and user notifications. Supporting this core are three additional files:
UpdateServiceSupport.swift– Provides semantic version comparison logic and release candidate selectionUpdateInstallerSupport.swift– Handles download validation and installation orchestrationUpdateHighlightsView.swift– Renders the update-available UI and release notes display
This modular design separates concerns between checking, selecting, and installing updates.
Initialization and Automatic Scheduling
When the application launches, the entry point calls UpdateService.shared.startAutomaticChecks(). This method, implemented around lines 62-98 of UpdateService.swift, performs three critical setup tasks:
- Loads persisted preferences – Retrieves previous install results and default settings for beta updates
- Schedules periodic checks – Invokes
configureAutomaticChecks()to create an hourlyTimer(60-minute interval) that repeatedly callscheck(manual: false) - Queues immediate validation – Schedules a one-off delayed check 6 seconds after launch (lines 80-84) to ensure users see availability promptly without blocking startup
The service persists user preferences through DefaultsKey.autoCheckUpdates and DefaultsKey.includeBetaUpdates, defined in Sources/Vorssaint/Core/Defaults.swift.
User-Configurable Update Preferences
The system exposes two boolean flags that control check behavior:
autoCheckEnabled (default: true, lines 40-46)
Toggles the hourly background timer. When disabled, the app only checks when manually requested.
includeBetaUpdates (default: matches build type, lines 48-55)
Determines whether pre-release versions appear in update notifications. Beta builds default this to true, while stable builds default to false.
These settings integrate with the UI through standard UserDefaults bindings, allowing real-time preference changes without restarts.
The Update Check Execution Flow
The check(manual:) method (starting line 16) implements the actual network logic with several safeguards:
API Endpoint Selection
The method aborts immediately if running in a developer build or if a check is already in progress (lines 16-21). Based on includeBetaUpdates, it selects between:
- Latest release endpoint – Used when beta updates are disabled
- List-of-last-10 endpoint – Used when beta updates are enabled to scan recent pre-releases
Request Configuration
All requests include proper Accept and User-Agent headers and explicitly disable local caching using .reloadIgnoringLocalCacheData (lines 25-28) to ensure fresh data.
Version Parsing and Selection
Upon receiving JSON, the decoder maps responses into GitHubRelease objects. The system scans each release for assets ending with .dmg, creating ReleaseCandidate objects containing:
- Semantic version tag
- Pre-release flag
- Direct DMG download URL
- File size and release notes
The selection logic resides in UpdateServiceSupport.selectUpdate (lines 99-140 of UpdateServiceSupport.swift), which applies semantic-version comparison rules against the current app version while respecting the beta-inclusion preference.
State Management and User Notifications
UpdateService maintains a reactive state property that drives UI updates through Combine publishers. The possible states include:
.available(version:)– Triggered whenselectUpdateidentifies a newer version; populatesavailableNoteswith formatted release notes and posts a macOS system notification.upToDate– Set when no newer version exists.failed(String)– Captures network failures, malformed JSON, or GitHub API errors (lines 33-38).checking– Indicates an active request
Notifications are suppressed during manual checks or when the version was previously advertised to prevent alert fatigue.
Staleness-Aware Rechecking
To ensure freshness without API abuse, UI components call checkIfStale(maxAge:) when becoming active (e.g., when the menu bar panel opens). If the last successful check exceeds the supplied maxAge (default 15 minutes), the method triggers a fresh check(manual: false) (lines 92-104). This pattern ensures users see critical updates immediately upon interaction while maintaining the 60-minute background interval.
Complete Implementation Examples
Starting Automatic Checks
Typically invoked from AppDelegate or the SwiftUI app initializer:
// In application didFinishLaunching or @main struct init
UpdateService.shared.startAutomaticChecks()
Triggering Manual Checks
Connect to a "Check for Updates" button:
@IBAction func checkForUpdates(_ sender: Any) {
UpdateService.shared.check(manual: true)
}
Reacting to State Changes
Bind to the service's state publisher to update UI components:
private var cancellable: AnyCancellable?
func bindUpdateState() {
cancellable = UpdateService.shared.$state.sink { state in
switch state {
case .idle, .checking: // show spinner
self.updateStatusView.showProgress()
case .upToDate:
self.updateStatusView.showMessage("You’re up‑to‑date")
case .available(let version):
self.updateStatusView.showUpdateAvailable(version: version)
case .downloading(let progress):
self.updateStatusView.showDownloadProgress(progress ?? 0)
case .installing:
self.updateStatusView.showMessage("Installing…")
case .failed(let error):
self.updateStatusView.showError(error)
}
}
}
Configuring Beta Update Preferences
Link a settings toggle to the service:
// Toggle in Settings UI
@IBAction func toggleBetaUpdates(_ sender: NSButton) {
UpdateService.shared.includeBetaUpdates = (sender.state == .on)
}
Summary
- vorssaint-utils implements update checks through
UpdateService.swift, which schedules hourly automatic polls viaTimerand immediate staleness checks every 15 minutes during UI interaction - The system queries the GitHub Releases API with cache-busting headers, selecting between latest-release and recent-releases endpoints based on beta preferences
- Semantic version comparison occurs in
UpdateServiceSupport.swift, filtering for.dmgassets and respecting pre-release flags - A state machine drives the user experience through Combine publishers, handling
.available,.upToDate, and.failedstates with appropriate macOS notifications - User defaults in
Defaults.swiftpersist preferences for automatic checking and beta inclusion across app launches
Frequently Asked Questions
How often does vorssaint-utils check for updates?
By default, vorssaint-utils performs a background check every 60 minutes through a repeating Timer created in configureAutomaticChecks(). Additionally, whenever the user opens specific UI panels or the app becomes active, checkIfStale(maxAge:) triggers a fresh check if the last successful query was more than 15 minutes ago.
Can I disable automatic update checks in vorssaint-utils?
Yes. The autoCheckEnabled property (backed by DefaultsKey.autoCheckUpdates) controls the hourly timer. Setting this to false prevents automatic background checks, though users can still manually trigger checks via check(manual: true).
How does vorssaint-utils handle beta versions?
The includeBetaUpdates flag determines whether pre-release versions appear in update notifications. When enabled, the service queries the list-of-last-10 releases endpoint to scan recent betas; when disabled, it queries only the latest stable release. Beta builds of vorssaint-utils default this setting to true, while stable builds default to false.
What happens when vorssaint-utils finds an available update?
When UpdateServiceSupport.selectUpdate identifies a newer version, the service transitions to the .available(version:) state, populates availableNotes with release notes, and posts a macOS system notification. The actual download and installation occur only when the user explicitly initiates the process through downloadAndInstall(), which validates the DMG size against downloadExpectedBytes before handing off to the installation script.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →