# Install Vaka in an iOS app

These steps are written for a coding agent. Do them in order and verify each one before moving on.

- Endpoint: `https://vaka.example.com`
- Ingest key: `vk_your_ingest_key` (write-only, safe to commit and ship in the app)
- Project: `your-project`

Requirements: iOS 15+ (macOS 12+ and tvOS 15+ also work), Swift 5.9+ / Xcode 15+.

## 1. Add the Swift package

The package is `https://github.com/ErikHellman/vaka`, product `Vaka`.

Pick the case that matches the project:

**A. The app is built from a `Package.swift`** (or you are adding Vaka to a local package that the app uses).
Add the dependency and the product:

```swift
dependencies: [
    .package(url: "https://github.com/ErikHellman/vaka", from: "0.1.0"),
],
targets: [
    .target(name: "MyApp", dependencies: [.product(name: "Vaka", package: "vaka")]),
]
```

If version resolution fails because no release tag exists yet, use `branch: "main"` instead of `from: "0.1.0"`.

**B. The project is generated by XcodeGen (`project.yml`) or Tuist (`Project.swift`).** Add the package there,
not in the `.xcodeproj`. XcodeGen:

```yaml
packages:
  Vaka:
    url: https://github.com/ErikHellman/vaka
    from: 0.1.0
targets:
  MyApp:
    dependencies:
      - package: Vaka
```

Then regenerate the project (`xcodegen generate` / `tuist generate`).

**C. A plain `.xcodeproj`.** Editing `project.pbxproj` by hand is error-prone. Ask the human to add it in Xcode:
*File > Add Package Dependencies…*, paste `https://github.com/ErikHellman/vaka`, and add the **Vaka** library to the
app target. Continue with step 2 while they do; the code will not compile until the package is added.

## 2. Start the SDK at launch

Start Vaka as early as possible, before other SDKs that install crash handlers.

SwiftUI app (`@main struct ...: App`):

```swift
import SwiftUI
import Vaka

@main
struct MyApp: App {
    init() {
        Vaka.start(Vaka.Options(
            key: "vk_your_ingest_key",
            endpoint: URL(string: "https://vaka.example.com")!,
            environment: {
                #if DEBUG
                "debug"
                #else
                "production"
                #endif
            }()
        ))
    }

    var body: some Scene {
        WindowGroup { ContentView() }
    }
}
```

UIKit app (`AppDelegate`):

```swift
import UIKit
import Vaka

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    Vaka.start(Vaka.Options(key: "vk_your_ingest_key", endpoint: URL(string: "https://vaka.example.com")!))
    return true
}
```

`release` defaults to `CFBundleShortVersionString` (the app's `MARKETING_VERSION`). Leave it, or pass the same
string you will use for the dSYM upload in step 4.

The SDK then automatically:

- reports crashes (signals and uncaught Objective-C exceptions) on the next launch,
- tracks sessions for crash-free rates (start on foreground, end after 30 s in background),
- queues everything on disk and sends in gzipped batches every 10 s, retrying when offline.

## 3. Add screen views, events and handled errors

Add these where they fit the app. Do not add user identification unless the human asks for it.

```swift
Vaka.screen("Checkout")                         // in .onAppear / viewDidAppear of main screens
Vaka.track("purchase", ["plan": "pro"])         // key product events; values are String, number or Bool
Vaka.addBreadcrumb(category: "cart", message: "Added item 42")

do {
    try checkout()
} catch {
    Vaka.capture(error)                         // handled errors you want to see
}

Vaka.identify("user-123")                       // opt-in only; Vaka.identify(nil) on logout
```

For SwiftUI, a simple pattern is `.onAppear { Vaka.screen("Settings") }` on each top-level screen view.

## 4. Upload dSYMs so crashes get function names, files and lines

Crash reports contain raw addresses. The server symbolicates them with the dSYM uploaded for the same build.

1. Make sure release builds produce dSYMs: build setting `DEBUG_INFORMATION_FORMAT = dwarf-with-dsym`
   for the Release configuration (this is Xcode's default).
2. Install the CLI on the machine that builds releases (developer Mac or CI):
   `cargo install --git https://github.com/ErikHellman/vaka vaka`
3. Create an API token with write access to the project (admin UI at `https://vaka.example.com/ui`, or ask the human).
   Never commit it: put it in CI secrets, or in a git-ignored `.vaka.env` file next to the `.xcodeproj`:
   `VAKA_TOKEN=vt_...`
4. Add a **Run Script** build phase as the last phase of the app target:

   ```sh
   # Upload dSYMs to Vaka (release builds only).
   [ "$CONFIGURATION" = "Release" ] || exit 0
   [ -f "$SRCROOT/.vaka.env" ] && . "$SRCROOT/.vaka.env" && export VAKA_TOKEN
   if ! command -v vaka >/dev/null 2>&1; then
     [ -x "$HOME/.cargo/bin/vaka" ] && PATH="$HOME/.cargo/bin:$PATH" || { echo "warning: vaka CLI not installed; dSYMs not uploaded"; exit 0; }
   fi
   [ -n "$VAKA_TOKEN" ] || { echo "warning: VAKA_TOKEN not set; dSYMs not uploaded"; exit 0; }
   vaka upload dsym --url "https://vaka.example.com" --project "your-project" --release "$MARKETING_VERSION" "$DWARF_DSYM_FOLDER_PATH" \
     || echo "warning: Vaka dSYM upload failed"
   ```

   - Input file: `${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${EXECUTABLE_NAME}`
     (so Xcode runs the phase after the dSYM exists).
   - Xcode 15+ sandboxes build scripts: set `ENABLE_USER_SCRIPT_SANDBOXING = NO` on the app target, or the script
     cannot read the dSYM folder and `.vaka.env`.

   For XcodeGen, add the same script under `postBuildScripts` with `inputFiles`.

**CI / archives instead of a build phase:** after `xcodebuild archive`, run
`vaka upload dsym --url "https://vaka.example.com" --project "your-project" --release "<version>" path/to/MyApp.xcarchive/dSYMs`.

**Without the CLI:** upload each DWARF file with curl (send `kind` before `file`):

```sh
curl -H "Authorization: Bearer $VAKA_TOKEN" \
  -F kind=native -F release="2.3.1" \
  -F file=@MyApp.app.dSYM/Contents/Resources/DWARF/MyApp \
  "https://vaka.example.com/api/v1/projects/your-project/artifacts"
```

`release` must equal the app's `CFBundleShortVersionString` (or the `release` passed to `Vaka.Options`).

## 5. Verify

1. Build and run the app once; confirm it compiles and launches. With `debug: true` in `Vaka.Options`, the SDK logs
   send failures to the console.
2. Check that data arrives: `vaka --url "https://vaka.example.com" --project "your-project" issues` (needs `VAKA_TOKEN`), the MCP
   tools, or the admin UI. Screen views and events show up in analytics within a few seconds of the next flush.
3. Optional crash test (run without the debugger attached, because Xcode's debugger catches the crash first):
   add a temporary button that calls `fatalError("Vaka test crash")`, tap it, relaunch the app, and look for an
   `EXC_BREAKPOINT` issue. Remove the button afterwards.

## Notes

- Do not install another crash reporter alongside Vaka; only one can own the signal handlers reliably.
- To disable the SDK (e.g. for UI tests), pass `enabled: false` in `Vaka.Options`.
- API reference and behaviour: [sdk-ios.md](../sdk-ios.md). Wire format: [ingest.md](../ingest.md).
