

# Sync

Synchronize device state and execute pending jobs.

---

## sync

Syncs the lock via Bluetooth: sets the lock's internal clock, retrieves battery level, and uploads activity logs to the server.

### Signature

```kotlin
suspend fun sync(
    deviceId: String,
    key: String,
    timeInSeconds: Long? = null,
    accessToken: String? = null,
    operationId: Int? = null,
): SyncResult
```

```swift
func sync(
    _ deviceId: String,
    key: String,
    timeInSeconds time: Int? = nil,
    syncToken token: String? = nil
) async throws -> SyncResult
```

### Parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `deviceId` | `String` | Yes | Bluetooth device name of the lock. |
| `key` | `String` | Yes | eKey with `GET_TIME`, `SET_TIME`, `GET_BATTERY_LEVEL`, `GET_LOGS` permissions. |
| `timeInSeconds` | `Long?` | No | Unix timestamp (seconds) to set on the lock. If omitted, SDK uses last server time. |
| `accessToken` | `String?` | OAuth only | OAuth access token for uploading activity logs. |
| `operationId` | `Int?` | No | Operation ID for tracking. |

### Return Type

```kotlin
data class SyncResult(val batteryLevel: Int)
```

```swift
public struct SyncResult: Sendable {
    public let batteryLevel: Int
}
```

| Field | Description |
|-------|-------------|
| `batteryLevel` | Battery level as percentage (0–100). |

### Error Codes

| Exception | Code | Description |
|-----------|------|-------------|
| `BluetoothException` | 708 | Bluetooth is off or unavailable. |
| `ConnectionException` | 12 | Device disconnected during sync. |
| `TimeoutException` | 703 | BLE operation exceeded the timeout. |
| `ApiException` | varies | Activity log upload failed. |

### Example

```kotlin
try {
    val result = sdk.sync(
        deviceId = "IGM401abc123",
        key = guestKey,
    )
    println("Battery: ${result.batteryLevel}%")
} catch (e: IglooWorksException.ConnectionException) {
    showError("unexpected bluetooth connection issue")
} catch (e: IglooWorksException.TimeoutException) {
    showError("Timed out, try again")
} catch (e: IglooWorksException) {
    showError("Sync failed: ${e.message}")
}
```

```swift
do {
    let result = try await sdk.sync(
        "IGM401abc123", 
        key: guestKey)

    print("Battery: \(result.batteryLevel)%")
} catch LockManagerError.deviceDisconnected {
    showError(message: "unexpected bluetooth connection issue")
} catch {
    showError(message: "Sync failed: \(error.localizedDescription)")
}
```

### Notes

- Sync reads all pending activity logs from the lock in a loop until no more remain.
- Logs are stored in a local database first, then batch-uploaded to the server.
- If `timeInSeconds` is omitted and no cached server time is available, the lock time is not updated.

---

## syncJob

Executes pending jobs (created via the API or iglooworks Dashboard) on the lock. Results are emitted as a Flow — one `SyncJobResult` per job.

Supported job types: `CREATE_PIN`, `DELETE_PIN`, `ADD_GROUP_KEY`, `DELETE_GROUP_KEY`.

### Signature

```kotlin
fun syncJob(
    deviceId: String,
    key: String,
    jobIds: List<String>? = null,
    accessToken: String?,
): Flow<SyncJobResult>
```

```swift
func syncJobs(
    _ jobIds: [String] = [],
    from deviceId: String,
    key: String,
    syncJobToken token: String? = nil
) async -> AsyncThrowingStream<SyncJobResult, Error>
```

### Parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `deviceId` | `String` | Yes | Bluetooth device name of the lock. |
| `key` | `String` | Yes | eKey with `CREATE_PIN`, `DELETE_PIN`, `ADD_GROUP_KEY`, `DELETE_GROUP_KEY` permissions. |
| `jobIds` | `List<String>?` | No | Specific job IDs to execute. If null, all pending jobs are fetched and executed. |
| `accessToken` | `String?` | OAuth only | OAuth access token for API calls. |

### Return Type

```kotlin
data class SyncJobResult(
    val jobId: String,
    val jobType: String,
    val status: JobStatus,
    val remainingJobs: Int,
    val reason: IglooWorksException? = null,
)
```

```swift
public struct SyncJobResult: Codable, Sendable {
    public let jobId: String
    public let jobType: String
    public let status: SyncJobStatus
    public let remainingJobs: Int
    public var reason: String? = nil
}

public enum SyncJobStatus: String, Codable, Sendable {
    case success = "success"
    case failed = "failed"
    case pending = "pending"
}
```

| Field | Description |
|-------|-------------|
| `jobId` | Server-assigned job identifier. |
| `jobType` | Job description string (e.g. `"CREATE_PIN"`). |
| `status` | `COMPLETE`, `FAILED`, or `PENDING`. |
| `remainingJobs` | Number of jobs remaining after this one. |
| `reason` | Exception if the job failed or was left pending (e.g. timeout). |

### Error Codes

| Exception | Code | Description |
|-----------|------|-------------|
| `ApiException` | varies | API call failed when fetching or updating job status. |
| `GenericException` | 1 | Unhandled error. |

### Example

```kotlin
try {
    sdk.syncJob(
        deviceId = "IGM401abc123",
        key = guestKey,
        accessToken = token,
    ).collect { result ->
        println("Job ${result.jobId}: ${result.status} (${result.remainingJobs} remaining)")
        if (result.status == JobStatus.FAILED) {
            println("  Reason: ${result.reason?.message}")
        }
    }
} catch (e: IglooWorksException.ApiException) {
    showError("Server error: ${e.message}")
} catch (e: IglooWorksException) {
    showError("Sync jobs failed: ${e.message}")
}
```

```swift
for try await result in await sdk.syncJobs(
    from: "IGM401abc123", 
    key: guestKey, 
    syncJobToken: token
) {
    print("Job \(result.jobId): \(result.status.rawValue) (\(result.remainingJobs) remaining)")
    if result.status == .failed {
        print("  Reason: \(result.reason ?? "unknown")")
    }
}
```

### Notes

- Jobs already marked `COMPLETE` or `FAILED` on the server are emitted as-is without re-execution.
- If a BLE operation fails with `TimeoutException`, `ConnectionException`, or `BluetoothException`, the job is left as `PENDING` (not marked failed on the server).
- `DuplicatePinException` during `CREATE_PIN` is treated as success (job marked `COMPLETE`).
- `PinNotFoundException` during `DELETE_PIN` is treated as success (PIN already removed).
- On iOS the method is named `syncJobs` (plural), takes job IDs as the first positional parameter (`_ jobIds: [String] = []`, empty means "all pending"), and `reason` on `SyncJobResult` is a plain `String?`, not an `Error`/exception type.
