{"id":40049444,"url":"https://github.com/brewkits/kmpworkmanager","last_synced_at":"2026-05-20T06:09:42.249Z","repository":{"id":332313457,"uuid":"1133323650","full_name":"brewkits/kmpworkmanager","owner":"brewkits","description":"KMP WorkManager - Unified API for scheduling and managing background tasks—one‑off, periodic, exact and chained jobs—featuring advanced triggers, structured logging, event‑driven completion, demo UI and docs.","archived":false,"fork":false,"pushed_at":"2026-04-06T02:50:35.000Z","size":9798,"stargazers_count":179,"open_issues_count":0,"forks_count":4,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-04-06T03:24:25.039Z","etag":null,"topics":["alarmmanager","backgound-sync","background-app-refresh","background-fetch","background-jobs","background-worker","bgtaskscheduler","compose-multiplatform","ios-background","kmp","kmp-library","kotlin","kotlin-library","kotlin-multiplatform-library","kotlin-multiplatform-mobile","periodic-background-jobs","periodic-tasks","scheduled-jobs","scheduled-tasks","work-manager-kotlin"],"latest_commit_sha":null,"homepage":"https://www.brewkits.dev","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/brewkits.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"docs/SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-13T07:33:38.000Z","updated_at":"2026-04-06T02:50:43.000Z","dependencies_parsed_at":null,"dependency_job_id":"fff8dbf0-c983-439c-b339-6a4abc4abf84","html_url":"https://github.com/brewkits/kmpworkmanager","commit_stats":null,"previous_names":["brewkits/kmpworkmanager"],"tags_count":16,"template":false,"template_full_name":null,"purl":"pkg:github/brewkits/kmpworkmanager","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brewkits%2Fkmpworkmanager","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brewkits%2Fkmpworkmanager/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brewkits%2Fkmpworkmanager/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brewkits%2Fkmpworkmanager/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/brewkits","download_url":"https://codeload.github.com/brewkits/kmpworkmanager/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brewkits%2Fkmpworkmanager/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31674718,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-11T08:18:19.405Z","status":"ssl_error","status_checked_at":"2026-04-11T08:17:08.892Z","response_time":54,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["alarmmanager","backgound-sync","background-app-refresh","background-fetch","background-jobs","background-worker","bgtaskscheduler","compose-multiplatform","ios-background","kmp","kmp-library","kotlin","kotlin-library","kotlin-multiplatform-library","kotlin-multiplatform-mobile","periodic-background-jobs","periodic-tasks","scheduled-jobs","scheduled-tasks","work-manager-kotlin"],"created_at":"2026-01-19T06:03:56.000Z","updated_at":"2026-05-20T06:09:42.241Z","avatar_url":"https://github.com/brewkits.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n\u003cimg src=\"assets/logo.svg\" height=\"160\" alt=\"KMP WorkManager\" /\u003e\n\n# KMP WorkManager\n\n[![Maven Central](https://img.shields.io/maven-central/v/dev.brewkits/kmpworkmanager?color=4F46E5\u0026label=Maven%20Central)](https://central.sonatype.com/artifact/dev.brewkits/kmpworkmanager)\n[![Kotlin](https://img.shields.io/badge/Kotlin-2.1.0-4F46E5?logo=kotlin\u0026logoColor=white)](https://kotlinlang.org)\n[![CI](https://github.com/brewkits/kmpworkmanager/actions/workflows/build.yml/badge.svg)](https://github.com/brewkits/kmpworkmanager/actions/workflows/build.yml)\n[![License](https://img.shields.io/badge/License-Apache%202.0-blue)](LICENSE)\n\n**Background task scheduling for Kotlin Multiplatform — including the parts iOS makes hard.**\n\n\u003c/div\u003e\n\n---\n\n## Installation\n\n```kotlin\n// build.gradle.kts\ncommonMain.dependencies {\n    implementation(\"dev.brewkits:kmpworkmanager:2.5.0\")\n}\n```\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eAndroid setup\u003c/b\u003e\u003c/summary\u003e\n\n```kotlin\nclass MyApplication : Application() {\n    override fun onCreate() {\n        super.onCreate()\n        KmpWorkManager.initialize(\n            context = this,\n            workerFactory = AppWorkerFactory() // Must implement AndroidWorkerFactory\n        )\n    }\n}\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eiOS setup\u003c/b\u003e\u003c/summary\u003e\n\n**1. AppDelegate**:\n\n```swift\n@main\nclass AppDelegate: UIResponder, UIApplicationDelegate {\n\n    override init() {\n        super.init()\n        // IOSModuleKt.iosModule calls kmpWorkerModule(workerFactory = IosWorkerFactoryGenerated())\n        KoinInitializerKt.doInitKoin(platformModule: IOSModuleKt.iosModule)\n    }\n\n    func application(_ application: UIApplication,\n                     didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -\u003e Bool {\n        let koin = KoinIOS()\n        \n        BGTaskScheduler.shared.register(\n            forTaskWithIdentifier: \"kmp_chain_executor_task\",\n            using: nil\n        ) { task in\n            IosBackgroundTaskHandler.shared.handleChainExecutorTask(\n                task: task,\n                chainExecutor: koin.getChainExecutor()\n            )\n        }\n        return true\n    }\n}\n```\n\n**2. `Info.plist`**:\n\n```xml\n\u003ckey\u003eBGTaskSchedulerPermittedIdentifiers\u003c/key\u003e\n\u003carray\u003e\n    \u003cstring\u003ekmp_chain_executor_task\u003c/string\u003e\n    \u003c!-- Add other worker bgTaskIds here --\u003e\n\u003c/array\u003e\n```\n\nFull setup: [docs/platform-setup.md](docs/platform-setup.md)\n\n\u003c/details\u003e\n\n---\n\n## Quick start\n\n### Schedule a task\n\n```kotlin\n// One-time — runs as soon as constraints are met\nscheduler.enqueue(\n    id              = \"nightly-sync\",\n    trigger         = TaskTrigger.OneTime(initialDelayMs = 0),\n    workerClassName = \"SyncWorker\",\n    constraints     = Constraints(requiresNetwork = true)\n)\n\n// Periodic — every 15 minutes\nscheduler.enqueue(\n    id              = \"heartbeat\",\n    trigger         = TaskTrigger.Periodic(intervalMs = 15 * 60 * 1000L),\n    workerClassName = \"SyncWorker\"\n)\n```\n\n### Define a worker\n\nYou can implement background logic in your `commonMain` code, but KMP WorkManager expects platform-specific factory registration. We recommend using `kmpworker-ksp` to auto-generate this boilerplate.\n\n```kotlin\n// commonMain — shared logic\nclass SyncWorker : Worker {\n    override suspend fun doWork(input: String?, env: WorkerEnvironment): WorkerResult {\n        val items = api.fetchPendingItems()\n        database.upsert(items)\n        return WorkerResult.Success(\"Synced ${items.size} items\")\n    }\n}\n```\n\n```kotlin\n// androidMain\nimport dev.brewkits.kmpworkmanager.annotations.Worker\n\n@Worker(name = \"SyncWorker\")\nclass SyncWorkerAndroid : AndroidWorker {\n    override suspend fun doWork(input: String?, env: WorkerEnvironment) =\n        SyncWorker().doWork(input, env)\n}\n\n// iosMain\nimport dev.brewkits.kmpworkmanager.annotations.Worker\n\n@Worker(name = \"SyncWorker\", bgTaskId = \"sync_task\")\nclass SyncWorkerIos : IosWorker {\n    override suspend fun doWork(input: String?, env: WorkerEnvironment) =\n        SyncWorker().doWork(input, env)\n}\n```\n\nThe `name` argument **must match** the `workerClassName` you pass to `scheduler.enqueue(...)` (`\"SyncWorker\"` above). Set it explicitly so ProGuard/R8 rename of the wrapper class doesn't break factory lookup.\n\n*Note: Use `AndroidWorkerFactoryGenerated()` and `IosWorkerFactoryGenerated()` in your DI/Initialization if you use KSP. Otherwise, manually implement `AndroidWorkerFactory` and `IosWorkerFactory`.*\n\n### Chain tasks\n\n```kotlin\n// Multi-step workflows that survive process death.\n// If step 47 of 100 was running when iOS killed the app —\n// the next BGTask invocation resumes at step 47, not step 0.\nscheduler.beginWith(TaskRequest(\"DownloadWorker\", inputJson = \"\"\"{\"url\":\"$fileUrl\"}\"\"\"))\n    .then(TaskRequest(\"ValidateWorker\"))\n    .then(TaskRequest(\"TranscodeWorker\"))\n    .then(TaskRequest(\"UploadWorker\", inputJson = \"\"\"{\"bucket\":\"processed\"}\"\"\"))\n    .withId(\"transcode-pipeline\", policy = ExistingPolicy.KEEP)\n    .enqueue()\n```\n\n---\n\n## Why KMP WorkManager?\n\nMost KMP libraries wrap the happy path — iOS BGTaskScheduler is not just \"a different API.\"\nIt has a credit system that punishes apps overrunning their time budget, an opaque scheduling policy,\nand no recovery mechanism for incomplete work. Getting it wrong means your tasks silently stop running.\n\n| | Android | iOS |\n|---|---------|-----|\n| Scheduling | Deterministic via WorkManager | Opportunistic — OS decides when |\n| Exact timing | ✅ AlarmManager | ⚠️ Best-effort |\n| Chain recovery | ✅ WorkContinuation | ✅ Step-level persistence |\n| Time budget enforcement | — | ✅ Adaptive (reserves 15–30% safety margin) |\n| Queue integrity | ✅ | ✅ CRC32-verified binary format |\n| Thread-safe expiry | ✅ | ✅ AtomicInt shutdown flag |\n\n---\n\n## Triggers\n\n| Trigger | Android | iOS | Notes |\n|---------|---------|-----|-------|\n| `OneTime(delayMs)` | WorkManager | BGTaskScheduler | Minimum delay may be enforced by OS |\n| `Periodic(intervalMs)` | WorkManager | BGTaskScheduler | Min 15 min on both platforms |\n| `Exact(epochMs)` | AlarmManager | Best-effort | iOS cannot guarantee exact timing |\n| `Windowed(earliest, latest)` | WorkManager with delay | BGTaskScheduler | Preferred over Exact on iOS |\n| `ContentUri(uri)` | WorkManager ContentUriTrigger | — | Android only |\n\n---\n\n## What's new in v2.5.0\n\nv2.5.0 is a hardening release driven by a production architecture review for camera-app workloads. Highlights:\n\n- **Parallel HTTP download/upload workers** — `ParallelHttpDownloadWorker` splits one\n  file into N HTTP `Range` chunks (default 4, up to 16) with per-chunk `.partN` resume;\n  `ParallelHttpUploadWorker` runs one POST per file under a `maxConcurrent` semaphore.\n- **Checksum verification** for `HttpDownloadWorker` — `expectedChecksum` +\n  `ChecksumAlgorithm` (MD5 / SHA-1 / SHA-256 / SHA-512) via Okio's `HashingSource`.\n- **DuplicatePolicy** on `HttpDownloadConfig` — `OVERWRITE` (default, preserves\n  pre-v2.5 behaviour), `SKIP` (return Success without network), `RENAME` (append `_1`,\n  `_2`, … to the stem).\n- **iOS background URLSession download** — `IosBackgroundDownloadWorker` survives\n  full app termination; persisted state store ensures cold-launch completion events\n  are delivered to the right `savePath` (the P0 bug fixed in this release).\n- **iOS chain retry honoring** — `WorkerResult.Retry(delayMs, attemptCap)` is now\n  honored at the chain executor level on iOS via `ChainProgress.stepRetryCounts` +\n  `ChainExecutor.requestedNextBgTaskDelayMs`.\n- **Android FGS type configurable** — `KmpHeavyWorker.foregroundServiceType` is\n  overrideable. Companion-object aliases (`FGS_DATA_SYNC`, `FGS_MEDIA_PROCESSING`,\n  `FGS_CAMERA`, …) make camera-app workloads first-class.\n- **Adversarial test coverage** — collision proof for `PendingIntent` request codes\n  (CRC32 vs `String.hashCode`), `BroadcastReceiver` lifecycle (Robolectric), iOS\n  per-step retry counter, backward-compat with v2.4.3-shaped JSON files, cold-launch\n  survival for background URLSession state.\n- **Hard-limit docs** — [`docs/IOS_BGTASK_LIMITS.md`](docs/IOS_BGTASK_LIMITS.md),\n  [`docs/ANDROID_FGS_GUIDE.md`](docs/ANDROID_FGS_GUIDE.md),\n  [`docs/APPLE_APP_STORE_REVIEW_GUIDELINES.md`](docs/APPLE_APP_STORE_REVIEW_GUIDELINES.md).\n\nFull breakdown: [`CHANGELOG.md`](CHANGELOG.md). Upgrade notes for users on v2.4.x:\n[`docs/MIGRATION_V2.5.0.md`](docs/MIGRATION_V2.5.0.md).\n\n## What's new in v2.4.3\n\n### iOS Dynamic Task IDs (no more Info.plist for every task)\n\nPreviously, every task ID had to be declared in `BGTaskSchedulerPermittedIdentifiers` before scheduling. v2.4.3 removes that constraint: only the single master dispatcher ID needs to be in `Info.plist`. All other task IDs are routed through an internal `AppendOnlyQueue` and executed when the OS fires the master dispatcher slot.\n\n```kotlin\n// This ID does NOT need to be in Info.plist\nscheduler.enqueue(\n    id = \"user-${userId}-daily-sync\",   // dynamic, per-user ID\n    trigger = TaskTrigger.Periodic(intervalMs = 24 * 60 * 60 * 1000),\n    workerClassName = \"DailySyncWorker\"\n)\n```\n\n```xml\n\u003c!-- Info.plist — only one entry needed for all dynamic tasks --\u003e\n\u003ckey\u003eBGTaskSchedulerPermittedIdentifiers\u003c/key\u003e\n\u003carray\u003e\n    \u003cstring\u003ekmp_master_dispatcher_task\u003c/string\u003e\n    \u003cstring\u003ekmp_chain_executor_task\u003c/string\u003e\n\u003c/array\u003e\n```\n\n### Periodic Task Improvements\nAdded granular control over the first execution of periodic tasks. You can now defer the initial run or set a specific delay, ensuring your app doesn't choke on heavy sync tasks immediately upon startup.\n\n```kotlin\n// Run every 1 hour, but defer the very first run by 1 hour\nTaskTrigger.Periodic(\n    intervalMs = 3600_000,\n    runImmediately = false\n)\n```\n\n### Swift Interop 2.0\niOS developers can now use idiomatic `Double` (seconds) instead of `Long` (milliseconds) for all triggers, making the API feel native to the Apple ecosystem.\n\n```swift\n// Swift\nlet trigger = createTaskTriggerPeriodicSeconds(\n    intervalSeconds: 3600, \n    initialDelaySeconds: 600\n)\n```\n\n### iOS Native Background Task Handler\nThe host application no longer needs to copy and maintain 150+ lines of Swift boilerplate to handle iOS background tasks. The library now provides a native Kotlin API that handles the entire lifecycle:\n\n```swift\n// AppDelegate.swift — now just 3 lines to handle any task\nBGTaskScheduler.shared.register(forTaskWithIdentifier: taskId, using: nil) { task in\n    IosBackgroundTaskHandler.shared.handleSingleTask(\n        task: task,\n        scheduler: koin.getScheduler(),\n        executor: koin.getExecutor()\n    )\n}\n```\n\nThis handler automatically:\n- Sets up the `expirationHandler` for graceful shutdown.\n- Resolves worker metadata (`workerClassName`, `inputJson`) from file storage.\n- Executes the worker with timeout protection via `SingleTaskExecutor`.\n- **Auto-reschedules periodic tasks** and the chain executor if the queue is not empty.\n- Performs deadline checks for windowed tasks.\n\n### Hardened iOS Persistence \u0026 Safety\nWe've overhauled the iOS storage engine to ensure industrial-grade reliability:\n- **Okio Streaming**: All file operations (Events, History, Queue) now use Okio. Peak RAM usage is now constant (O(1)) regardless of file size, preventing OOM kills on older devices.\n- **Race Condition Fixes**: Critical fix in `IosFileCoordinator` using `AtomicInt` to ensure background blocks are executed exactly once, even during high-concurrency stress or timeouts.\n- **Self-Healing Queue**: We now detect CRC32 checksum mismatches and automatically recover/reset corrupted records to prevent persistent job stalls.\n- **UTF-8 Safety**: Guaranteed safety against multi-byte character corruption (Emoji/CJK) at chunk boundaries.\n\n### Execution history (v2.3.8)\nEvery chain execution is persisted locally. Collect, upload, clear:\n\n```kotlin\nlifecycleScope.launch {\n    val records = scheduler.getExecutionHistory(limit = 200)\n    if (records.isNotEmpty()) {\n        analyticsService.uploadBatch(records)\n        scheduler.clearExecutionHistory()\n    }\n}\n```\n\nEach `ExecutionRecord` carries `chainId`, `status` (SUCCESS / FAILURE / ABANDONED / SKIPPED / TIMEOUT), `durationMs`, step counts, error message, retry count, and platform. Up to 500 records kept; older ones pruned automatically.\n\n### Telemetry hook\nRoute task lifecycle events to Sentry, Crashlytics, or Datadog:\n\n```kotlin\nKmpWorkManagerConfig(\n    telemetryHook = object : TelemetryHook {\n        override fun onTaskFailed(event: TelemetryHook.TaskFailedEvent) {\n            Sentry.captureMessage(\"Task failed: ${event.taskName} — ${event.error}\")\n        }\n        override fun onChainFailed(event: TelemetryHook.ChainFailedEvent) {\n            analytics.track(\"chain_failed\", mapOf(\n                \"chainId\"   to event.chainId,\n                \"failedStep\" to event.failedStep\n            ))\n        }\n    }\n)\n```\n\nSix events: `onTaskStarted`, `onTaskCompleted`, `onTaskFailed`, `onChainCompleted`, `onChainFailed`, `onChainSkipped`. All have default no-op implementations.\n\n### Task priority\n`LOW`, `NORMAL`, `HIGH`, `CRITICAL`. On Android, `HIGH`/`CRITICAL` map to expedited work. On iOS, the queue is sorted by priority before each BGTask window:\n\n```kotlin\nscheduler.beginWith(\n    TaskRequest(workerClassName = \"PaymentSyncWorker\", priority = TaskPriority.CRITICAL)\n).enqueue()\n```\n\n### Battery guard\n```kotlin\nKmpWorkManagerConfig(minBatteryLevelPercent = 10) // defer when \u003c 10% and not charging\n```\nDefault `5%`. Works on both platforms.\n\n### KSP: BGTask ID validation\n\n```kotlin\n// iosMain\n@Worker(\"SyncWorker\", bgTaskId = \"com.example.sync-task\")\nclass SyncWorker : IosWorker { ... }\n\n// kmpWorkerModule() automatically validates bgTaskId against Info.plist at startup\nkmpWorkerModule(workerFactory = IosWorkerFactoryGenerated())\n```\n\nAdd to `build.gradle.kts`:\n```kotlin\nplugins { id(\"com.google.devtools.ksp\") }\n\ndependencies {\n    ksp(\"dev.brewkits:kmpworker-ksp:2.5.0\")\n    commonMain.implementation(\"dev.brewkits:kmpworker-annotations:2.5.0\")\n}\n```\n\n---\n\n## Built-in workers\n\n| Worker | Status | Notes |\n|--------|--------|-------|\n| `HttpRequestWorker` | Stable | One-shot HTTP with configurable method, headers, body. SSRF-validated. |\n| `HttpDownloadWorker` | Stable (v2.5+) | Resumable download via HTTP `Range`. `\u003csavePath\u003e.partial` survives process kill; a process kill resumes from last byte. Supports SHA-256/SHA-1/SHA-512/MD5 checksum verification and `DuplicatePolicy` (overwrite / skip / rename). |\n| `ParallelHttpDownloadWorker` | New in v2.5 | Splits a single file into N (1..16, default 4) HTTP `Range` chunks downloaded concurrently with per-chunk `.partN` resume. Automatic sequential fallback when the server does not advertise `Accept-Ranges: bytes`. Same checksum verification surface as `HttpDownloadWorker`. |\n| `HttpUploadWorker` | ⚠️ Experimental | Streaming multipart upload. No resumable / chunked upload yet (see `ParallelHttpUploadWorker` for multi-file uploads). |\n| `ParallelHttpUploadWorker` | New in v2.5 | One POST per file with per-host `maxConcurrent` limit (1..16, default 3) and per-file retry on 5xx / network errors (`maxRetries` 0..5). Per-file outcomes exposed via `WorkerResult.Success.data.fileResults`. |\n| `IosBackgroundDownloadWorker` | iOS-only, experimental (v2.5+) | Hands the download to `URLSessionConfiguration.background` so the transfer survives **full app termination**. Host AppDelegate must wire `application(_:handleEventsForBackgroundURLSession:completionHandler:)` — see [docs/IOS_BACKGROUND_URL_SESSION.md](docs/IOS_BACKGROUND_URL_SESSION.md). |\n| `HttpSyncWorker` | Stable | Fetch-and-persist data sync. |\n| `FileCompressionWorker` | ✅ Android · 🚧 iOS | **iOS has no ZIP codec in Kotlin/Native.** The default behavior on iOS is to **fail fast** with an explicit error. Set `FileCompressionConfig.allowIosUncompressedFallback = true` to accept an uncompressed copy at the output path (useful for demo chains; the output is **not** a real ZIP). For real iOS compression, integrate [ZIPFoundation](https://github.com/weichsel/ZIPFoundation) via cinterop. |\n\n\u003e **Camera / media-app advisory.** For burst upload (50 photos at once), use\n\u003e `ParallelHttpUploadWorker` instead of one chain step per file. For RAW / video\n\u003e downloads over cellular, prefer `IosBackgroundDownloadWorker` on iOS so the\n\u003e transfer survives swipe-to-quit. `HttpUploadWorker` is the only stable worker\n\u003e without resumable/chunked semantics — pin those uploads to Wi-Fi\n\u003e (`Constraints(requiresUnmeteredNetwork = true)`) until v2.6.\n\n---\n\n## Security\n\n**SSRF protection** — all built-in worker HTTP calls are validated before dispatch. Blocked:\n\n```\n169.254.169.254   AWS/GCP/Azure IMDS\nfd00:ec2::254     AWS EC2 (IPv6)\n100.100.100.200   Alibaba Cloud metadata\nlocalhost, 0.0.0.0/8, [::1], 10.x, 172.16–31.x, 192.168.x\n100.64.0.0/10     CGNAT (Tailscale, carrier-grade NAT)\nfc00::/7, fe80::/10\n```\n\nRFC 3986 UserInfo bypass and multi-`@` authority attacks are both handled. DNS rebinding is a known limitation — use certificate pinning or an egress proxy for high-trust environments.\n\n**Input size validation** — inputs exceeding WorkManager's 10 KB `Data` limit throw `IllegalArgumentException` at enqueue time.\n\n---\n\n## Testing\n\n```\n600+ tests across commonTest, iosTest, androidInstrumentedTest\n```\n\n- `QA_PersistenceResilienceTest` — 100-step chain killed at step 50, resumes at exactly step 50\n- `V236ChainExecutorTest` — time budget, shutdown propagation, batch loop correctness\n- `IosExecutionHistoryStoreTest` — save/get/clear, auto-pruning, all status variants\n- `AppendOnlyQueueTest` — CRC32 corruption detection, truncation recovery, concurrent access\n- `SecurityValidatorTest` — SSRF, IPv6 compressed loopback, multi-`@` UserInfo bypass\n\n---\n\n## Documentation\n\n| | |\n|---|---|\n| [Quick Start](docs/quickstart.md) | Running in 5 minutes |\n| [Platform Setup](docs/platform-setup.md) | Android \u0026 iOS configuration |\n| [API Reference](docs/api-reference.md) | Full public API |\n| [Task Chains](docs/task-chains.md) | Chain API and recovery semantics |\n| [Built-in Workers](docs/BUILTIN_WORKERS_GUIDE.md) | Worker reference and input schema |\n| [Constraints \u0026 Triggers](docs/constraints-triggers.md) | All scheduling options |\n| [iOS Best Practices](docs/ios-best-practices.md) | BGTask gotchas and recommendations |\n| [iOS BGTask Hard Limits](docs/IOS_BGTASK_LIMITS.md) | Opportunistic scheduling, time budget, headless DI |\n| [App Store Review Compliance](docs/APPLE_APP_STORE_REVIEW_GUIDELINES.md) | §2.5.4 — what gets rejected and how to ship safely |\n| [Android FGS Type Guide](docs/ANDROID_FGS_GUIDE.md) | `mediaProcessing` / `camera` / `dataSync` setup |\n| [iOS Background URLSession](docs/IOS_BACKGROUND_URL_SESSION.md) | Surviving app termination during long downloads |\n| [Troubleshooting](docs/TROUBLESHOOTING.md) | Common issues |\n| [CHANGELOG](CHANGELOG.md) | Release history |\n\n**Migration:** [v2.2.2 → v2.3.0](docs/MIGRATION_V2.3.0.md) · [v2.3.3 → v2.3.4](docs/MIGRATION_V2.3.3_TO_V2.3.4.md) · [v2.4.x → v2.5.0](docs/MIGRATION_V2.5.0.md)\n\n---\n\n## Requirements\n\n| | |\n|---|---|\n| Kotlin | 2.1.0+ |\n| Android | 8.0+ (API 26) |\n| iOS | 13.0+ |\n| Gradle | 8.0+ |\n\n---\n\n## Contributing\n\n```bash\n./gradlew :kmpworker:allTests   # all platforms must pass before opening a PR\n```\n\nCommit messages follow [Conventional Commits](https://www.conventionalcommits.org/).\n\n---\n\n## License\n\nApache 2.0. See [LICENSE](LICENSE).\n\n---\n\n\u003cdiv align=\"center\"\u003e\n\n[GitHub](https://github.com/brewkits/kmpworkmanager) · [Maven Central](https://central.sonatype.com/artifact/dev.brewkits/kmpworkmanager) · [Issues](https://github.com/brewkits/kmpworkmanager/issues)\n\n\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrewkits%2Fkmpworkmanager","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbrewkits%2Fkmpworkmanager","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrewkits%2Fkmpworkmanager/lists"}