SDK
Swift
iOS 15+, macOS 12+, tvOS and watchOS. Zero dependencies: Ed25519 comes from CryptoKit, which your app already has.
Install
.package(url: "https://github.com/ripstop-dev/ripstop-swift", from: "0.1.0")Quickstart
import Ripstop
import SwiftUI
@main
struct MyApp: App {
@State private var gate: Ripstop?
var body: some Scene {
WindowGroup {
if let gate {
RipstopShell(gate: gate, onOpenURL: { UIApplication.shared.open($0) }) {
ContentView()
}
} else {
ContentView().task {
gate = await Ripstop.initialize(apiKey: "rs_pub_your_key")
}
}
}
}
}Headless
switch await ripstop.check() {
case .forceUpdate(let title, let body, let storeUrl):
break // Blocked. Not dismissible.
case .softUpdate(let title, let body, _, let canSnooze):
break // A nudge. await ripstop.snooze() records it.
case .maintenance(let title, let message, let endsAt, _, _, _):
break // Down on purpose. endsAt is display-only.
case .none:
break // Carry on.
}It is an actor
Ripstop is a Swift actor, so the cache and the snooze ledger are safe to touch from anywhere without you holding a lock. Every method is awaited; none of them throw.
Remote config
let enabled = await ripstop.values["checkout_enabled"]?.boolValue ?? true
let limit = await ripstop.values["upload_limit"]?.intValue ?? 10Always pass a fallback. On a first launch with no network there is no payload yet. That is the fail-open path working as intended.
Options
| Option | Default | What it does |
|---|---|---|
apiKey | no default | Your app’s public SDK key. Safe to ship |
appVersion | no default | The running build; rules evaluate against it |
locale | en | Which wall copy to resolve; falls back to en per key |
minFetchInterval | 6 hours | How long a payload is fresh enough to skip the network |
timeout | 5 seconds | Fetch budget. After that, cache |
storage | UserDefaults | Swap for Keychain, or InMemoryStorage |
signingKeys | pinned | Override for self-hosted deployments and tests |
session | .shared | Inject your own URLSession for tests or a proxy |
UserDefaults is fine here
The cache lives in UserDefaults, which is not a secret store and does not need to be: everything in it is signed, and a payload that has been tampered with fails verification rather than being believed. Swap in Keychain if your threat model differs.
Source
- github.com/ripstop-dev/ripstop-swift (MIT).
- 13 test cases, covering all 62 golden vectors plus the failure paths.