> ## Documentation Index
> Fetch the complete documentation index at: https://docs.r2vault.app/llms.txt
> Use this file to discover all available pages before exploring further.

# UpdateService

> GitHub releases integration for automatic app updates

## Overview

`UpdateService` fetches the latest release metadata from the GitHub API and determines if a newer version is available. It works in conjunction with [AppUpdater](/dev/services/app-updater) which handles the actual DMG download and installation.

<Info>
  Located at: `Fiaxe/Services/UpdateService.swift:32`
</Info>

<Note>
  **Two-Part Update System**

  * **UpdateService** (this page) - Fetches release metadata from GitHub API
  * **[AppUpdater](/dev/services/app-updater)** - Downloads DMG and installs updates

  Together they provide automatic updates by checking GitHub releases and installing new versions.
</Note>

## UpdateService

### Type Definition

```swift theme={null}
enum UpdateService
```

Implemented as an enum with static methods (no instances).

### checkForUpdate()

Fetches the latest release from GitHub and returns it if newer than the running version.

```swift theme={null}
static func checkForUpdate() async throws -> GitHubRelease?
```

**Returns:** `GitHubRelease` if a newer version is available, `nil` if current version is up to date.

**Throws:** Network errors or JSON decoding errors.

#### Implementation Details

```swift theme={null}
// Example from UpdateService.swift:36-50
static func checkForUpdate() async throws -> GitHubRelease? {
    var request = URLRequest(url: apiURL, cachePolicy: .reloadIgnoringLocalCacheData)
    request.setValue("application/vnd.github+json", forHTTPHeaderField: "Accept")

    let (data, _) = try await URLSession.shared.data(for: request)
    let release = try JSONDecoder().decode(GitHubRelease.self, from: data)

    let latestVersion = release.tagName.trimmingCharacters(in: .init(charactersIn: "v"))
    let currentVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0"

    if latestVersion.compare(currentVersion, options: .numeric) == .orderedDescending {
        return release
    }
    return nil
}
```

The method:

1. Fetches the latest release from `https://api.github.com/repos/xaif/r2Vault/releases/latest`
2. Sets Accept header to `application/vnd.github+json` for GitHub API v3
3. Decodes the JSON response into a `GitHubRelease` object
4. Strips leading "v" from tag name (e.g., "v1.2.3" → "1.2.3")
5. Compares versions numerically (1.2.3 \< 1.10.0)
6. Returns release if newer, `nil` otherwise

### API URL

```swift theme={null}
private static let apiURL = URL(string: "https://api.github.com/repos/xaif/r2Vault/releases/latest")!
```

Points to the GitHub Releases API for the r2Vault repository.

## GitHub Data Types

### GitHubRelease

Represents a GitHub release.

```swift theme={null}
struct GitHubRelease: Decodable {
    let tagName: String
    let htmlUrl: String
    let body: String?
    let assets: [GitHubAsset]
    
    var dmgDownloadURL: URL?
}
```

<ParamField path="tagName" type="String">
  Release version tag (e.g., `"v1.2.3"`).
</ParamField>

<ParamField path="htmlUrl" type="String">
  URL to the release page on GitHub.
</ParamField>

<ParamField path="body" type="String?">
  Release notes in Markdown format.
</ParamField>

<ParamField path="assets" type="[GitHubAsset]">
  Array of release assets (downloadable files).
</ParamField>

<ParamField path="dmgDownloadURL" type="URL?">
  Computed property returning the download URL for the first `.dmg` asset.
</ParamField>

#### dmgDownloadURL

```swift theme={null}
var dmgDownloadURL: URL? {
    assets.first(where: { $0.name.hasSuffix(".dmg") }).flatMap { URL(string: $0.browserDownloadUrl) }
}
```

Finds the first DMG asset and returns its download URL.

### GitHubAsset

Represents a release asset file.

```swift theme={null}
struct GitHubAsset: Decodable {
    let name: String
    let browserDownloadUrl: String
}
```

<ParamField path="name" type="String">
  Asset filename (e.g., `"R2Vault-1.2.3.dmg"`).
</ParamField>

<ParamField path="browserDownloadUrl" type="String">
  Direct download URL for the asset.
</ParamField>

## AppUpdater Integration

For complete documentation on the DMG download and installation process, see [AppUpdater](/dev/services/app-updater).

The AppUpdater handles:

* Downloading DMG files from GitHub releases
* Progress tracking during download
* Mounting and installing updates
* Safety checks for writable locations
* Automatic app replacement and relaunch

## Usage Examples

### Check for Updates

```swift theme={null}
import SwiftUI

struct UpdateCheckView: View {
    @State private var availableUpdate: GitHubRelease?
    @State private var isChecking = false
    @State private var errorMessage: String?
    
    var body: some View {
        VStack {
            if isChecking {
                ProgressView("Checking for updates...")
            } else if let update = availableUpdate {
                Text("Update available: \(update.tagName)")
                if let notes = update.body {
                    Text(notes)
                        .font(.caption)
                }
                Button("Install Update") {
                    AppUpdater.shared.install(release: update)
                }
            } else {
                Text("You're up to date!")
            }
            
            if let error = errorMessage {
                Text("Error: \(error)")
                    .foregroundStyle(.red)
            }
        }
        .task {
            await checkForUpdate()
        }
    }
    
    func checkForUpdate() async {
        isChecking = true
        errorMessage = nil
        defer { isChecking = false }
        
        do {
            availableUpdate = try await UpdateService.checkForUpdate()
        } catch {
            errorMessage = error.localizedDescription
        }
    }
}
```

### Download and Install

```swift theme={null}
struct UpdaterView: View {
    @State private var updater = AppUpdater.shared
    
    var body: some View {
        VStack {
            switch updater.state {
            case .idle:
                Text("Ready to update")
                
            case .downloading(let progress):
                ProgressView(value: progress) {
                    Text("Downloading: \(Int(progress * 100))%")
                }
                Button("Cancel") {
                    updater.cancel()
                }
                
            case .downloaded:
                Text("Update ready to install")
                Button("Install Now") {
                    updater.installDownloaded()
                }
                .buttonStyle(.borderedProminent)
                
            case .installing:
                ProgressView("Installing...")
                Text("The app will restart automatically")
                    .font(.caption)
                
            case .failed(let message):
                Text("Update failed: \(message)")
                    .foregroundStyle(.red)
                Button("Try Again") {
                    // Retry logic
                }
            }
        }
        .padding()
    }
}
```

### Automatic Updates

```swift theme={null}
@main
struct R2VaultApp: App {
    @State private var viewModel = AppViewModel()
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .task {
                    // Check for updates on launch
                    await checkForUpdatesOnLaunch()
                }
        }
    }
    
    func checkForUpdatesOnLaunch() async {
        // Wait a bit before checking (don't block app launch)
        try? await Task.sleep(for: .seconds(3))
        
        guard AppUpdater.shared.canSelfUpdate else {
            print("Self-update not available: \(AppUpdater.shared.updateBlockReason ?? "unknown")")
            return
        }
        
        do {
            if let release = try await UpdateService.checkForUpdate() {
                print("Update available: \(release.tagName)")
                // Show update notification or prompt
            }
        } catch {
            print("Update check failed: \(error)")
        }
    }
}
```

## Security Considerations

<Warning>
  **Update Security**

  The current implementation:

  * Uses HTTPS for GitHub API (transport security)
  * Removes quarantine attributes (`com.apple.quarantine`)
  * Does NOT verify code signatures
  * Does NOT verify DMG checksums

  For production apps, consider:

  * Verifying DMG checksums from GitHub release notes
  * Checking code signatures before installation
  * Using Apple's Sparkle framework for updates
  * Implementing delta updates for bandwidth efficiency
</Warning>

## App Store Distribution

<Note>
  If distributing via the Mac App Store:

  * Remove the auto-update system entirely
  * App Store handles all updates automatically
  * Self-updating violates App Store guidelines
  * Can still check GitHub API to notify users of updates
</Note>

## Related Services

* [MenuBarManager](/dev/services/menu-bar-manager) - Can display update notifications
* GitHub Releases API - Source of update metadata
