Binary distribution
GrovsSDK.xcframework.zip below is the prebuilt framework (device + simulator, library evolution enabled). Its module is GrovsSDK — use import GrovsSDK; Swift Package Manager and CocoaPods use import Grovs.
Supersedes 2.4, which was bumped in the podspec but never tagged or published.
Added
- Custom event tracking:
Grovs.track(_:properties:tags:),Grovs.setGlobalTags(_:), and
batched dispatch with retry. - Screen view tracking: automatic UIKit tracking,
Grovs.trackScreenView(_:properties:),
theGrovsScreenTrackingprotocol,Grovs.setScreenAliases(_:),
Grovs.screenNameProvider, and the SwiftUIView.grovsScreen()modifier. - Purchase tracking:
Grovs.logInAppPurchase(transactionID:completion:)and
Grovs.logCustomPurchase(type:priceInCents:currency:productID:startDate:completion:). - Clipboard-assisted deferred deep link matching, gated on a
gdparameter and a domain
allowlist (clipboardDomains:onconfigure), plus acopyToClipboardiOSlink flag. - Error reporting to the host app through the optional
GrovsDelegate.grovsDidEncounterError(_:message:). PrivacyInfo.xcprivacyprivacy manifest, shipped for SPM, CocoaPods, and the
xcframework.- A hosted test harness (
scripts/run_hosted_tests.sh) so the keychain tests assert
instead of skipping, and CI running tests, Thread Sanitizer, hosted keychain tests, and
a version-drift gate.
Changed
- Logging goes through
os_logand is redacted in release builds;Grovs.setDebug(level:)
now takes the publicLogLevel. - The device payload and purchase events carry
session_id. - The raw device identifier is sent instead of a mapped model name.
vendor_idis omitted whileidentifierForVendoris unavailable (before first unlock
after a reboot) instead of sending a shared"unknown"that the backend would match as
one device.- The privacy manifest declares collected data as linked to the user, since the host's
userIdentifieris stored against the same device record. - Custom purchases carry a client-minted
transaction_id, so a retry of a request the
backend already committed is deduplicated instead of counted twice. - A user agent fetched on an earlier launch is reused until the OS version changes, so
WebKit is no longer spun up on every cold launch. - CocoaPods ships the XIBs inside the
Grovsresource bundle rather than loose in the
host app, where a nib with the same name would collide. - Cached event archives require secure coding.
- Requests are never sent without a parseable User-Agent.
- The SDK version has a single source of truth (
GrovsVersion.current), checked against
the podspec andMARKETING_VERSIONin CI. - Internal types that were unnecessarily public are internal again; the supported surface
isGrovs,GrovsDelegate,GrovsError,GrovsScreenTracking,GrovsScreenName,
LogLevel,TransactionType,CustomLinkRedirect/CustomRedirects, and
View.grovsScreen().
Fixed
- Disabling the SDK now stops all collection — including identifier, attribute, and push
token updates, and lifecycle events already read from storage when consent is withdrawn —
and payloads without a link are still delivered. - Purchases persisted by an earlier launch are retried as soon as the SDK authenticates,
instead of waiting for the next foreground. - A superseded screen-alias response no longer discards newer aliases that are still
pending. - The message view stops its spinner when a page fails before the navigation commits
(offline, DNS, TLS). - Auto-displayed messages are no longer suppressed after a failed presentation, nor shown
twice by overlapping display chains. - Messages list pagination: a page superseded by a refresh is discarded, an older refresh
can't overwrite a newer one, and a failed page is retried rather than skipped. - Duplicate device resolves no longer cancel in-flight requests or release held events
early. - Keychain writes survive protection-class changes, and the SDK self-heals when the
backend rejects the device identifier. - Retain cycles, identity-recovery ordering, observer cleanup, and several data races
(the suite runs clean under Thread Sanitizer). - The
AutoScreenTrackerswizzle composes with otherviewDidAppearswizzlers, and no
longer replaces a completion block the host set on its ownCATransaction. - An unparseable
baseURLrefuses configuration and reportsfalseinstead of trapping. Retry-Afteris capped at 60 seconds.- The clipboard flow reads the pasteboard once, so a denied paste alert isn't shown twice.
Security
- Message content can only open
http,https,mailto,tel, andsmslinks
externally; every other scheme is ignored.