From 411afa70a204025e0ada8f532c2c25a7cb6eadb4 Mon Sep 17 00:00:00 2001 From: bdot <134958415+bdot-real@users.noreply.github.com> Date: Fri, 2 Oct 2026 14:28:42 -0400 Subject: [PATCH] Publish Score iOS SDK 0.2.0 --- INTEGRATION.md | 105 ++++++++++++++++--------- Package.swift | 13 +-- README.md | 38 ++++++--- ScoreTracker.podspec | 6 +- Sources/ScoreTracker/ScoreClient.swift | 6 +- 5 files changed, 111 insertions(+), 57 deletions(-) diff --git a/INTEGRATION.md b/INTEGRATION.md index 362b104..535b41a 100644 --- a/INTEGRATION.md +++ b/INTEGRATION.md @@ -8,57 +8,45 @@ identically — see [`conformance/CONTRACT.md`](conformance/CONTRACT.md). --- -## Install — 0.1.0 beta (hosted on Gitea) +## Install — 0.2.0 beta (public, no auth) -Everything is hosted on the org's Gitea — no external registry. The Android -artifacts live in **Gitea's Maven package registry**; the iOS XCFramework is a -**Gitea release asset** (`mobile-v0.1.0`). +Distributed publicly from **[`3HL-SCORE/public-sdks-score`](https://git.3halves-labs.com/3HL-SCORE/public-sdks-score)** (#284) — **no Gitea token / login required**. The iOS XCFramework is a public **release asset** (`mobile-v0.2.0`) on that repo; the Android artifacts live in the org's **Gitea Maven package registry** (anonymous read). -### Android — Gradle (Gitea Maven registry) +### Android — Gradle (public Maven registry) ```kotlin repositories { - maven { - url = uri("https://git.3halves-labs.com/api/packages/3HL-SCORE/maven") - // If the package registry isn't public, add a Gitea token: - // credentials(HttpHeaderCredentials::class) { name = "Authorization"; value = "token ${'$'}giteaToken" } - // authentication { create("header") } - } + maven { url = uri("https://git.3halves-labs.com/api/packages/3HL-SCORE/maven") } // public — no credentials } dependencies { - implementation("com.threehalveslabs.scoretracker:shared-android:0.1.0") + implementation("com.threehalveslabs.scoretracker:shared-android:0.2.0") } ``` -### iOS — CocoaPods (release asset) +### iOS — CocoaPods (public release asset) ```ruby pod 'ScoreTracker', - :podspec => 'https://git.3halves-labs.com/3HL-SCORE/score-anything-loyalty/raw/branch/main/mobile/ios/ScoreTracker/ScoreTracker.podspec' + :podspec => 'https://git.3halves-labs.com/3HL-SCORE/public-sdks-score/raw/branch/main/ScoreTracker.podspec' ``` -### iOS — Swift Package Manager (Gitea Swift registry) -Published to Gitea's **Swift package registry** as `threehalveslabs.ScoreTracker`. -Configure the registry once (per machine / CI), then add it as a normal -dependency by **id** — no git URL needed: -```bash -swift package-registry set https://git.3halves-labs.com/api/packages/3HL-SCORE/swift -# private registry → log in with a Gitea token: -swift package-registry login https://git.3halves-labs.com/api/packages/3HL-SCORE/swift --token -``` +### iOS — Swift Package Manager (public git URL) +The SPM package lives at the **root** of the public `public-sdks-score` repo, so +add it by **git URL** — no registry config, no login: ```swift // your app's Package.swift -dependencies: [ .package(id: "threehalveslabs.ScoreTracker", from: "0.1.0") ] -// then: .product(name: "ScoreTracker", package: "ScoreTracker") +dependencies: [ + .package(url: "https://git.3halves-labs.com/3HL-SCORE/public-sdks-score.git", from: "0.2.0") +], +// target: .product(name: "ScoreTracker", package: "public-sdks-score") ``` -The package's binary `ScoreTrackerKit` target pulls the XCFramework from the -`mobile-v0.1.0` release asset (SPM verifies the checksum). For a private release -asset, SPM reads credentials from `~/.netrc`. - -> Note: SPM-by-git-URL isn't available (the package lives in a monorepo subdir, -> which SPM can't resolve) — the Swift registry above is the supported path. +Or in Xcode: **File → Add Packages…** → paste the URL. The binary `ScoreTrackerKit` +target pulls the XCFramework from the **public** `mobile-v0.2.0` release asset (SPM +verifies the checksum) — no `~/.netrc` credentials needed. Verified with an +anonymous `swift package resolve`. ### iOS — direct XCFramework -Download `ScoreTrackerKit.xcframework.zip` from the **mobile-v0.1.0** release and -drag the `.xcframework` into your Xcode project; add `ScoreClient.swift`. +Download `ScoreTrackerKit.xcframework.zip` from the **mobile-v0.2.0** release on +[`public-sdks-score`](https://git.3halves-labs.com/3HL-SCORE/public-sdks-score/releases) +and drag the `.xcframework` into your Xcode project; add `ScoreClient.swift`. --- @@ -71,7 +59,7 @@ let score = ScoreClient( publishableKey: "pk_live_…", siteId: "urc-ios", endpoint: "https://", - sdkVersion: "0.1.0", + sdkVersion: "0.2.0", appVersion: Bundle.main.shortVersion, os: "iOS \(UIDevice.current.systemVersion)", device: UIDevice.current.model, @@ -106,7 +94,7 @@ val score = newScoreTracker( publishableKey = "pk_live_…", siteId = "urc-android", endpoint = "https://", - sdk = SdkInfo("@3halves-labs/score-tracker-android", "0.1.0"), + sdk = SdkInfo("@3halves-labs/score-tracker-android", "0.2.0"), device = DeviceDescriptor(os = "Android ${Build.VERSION.RELEASE}", device = Build.MODEL, appVersion = BuildConfig.VERSION_NAME, locale = Locale.getDefault().toLanguageTag()), ), @@ -152,6 +140,53 @@ Events declaring a category (e.g. `trackAdvertising(...)` on iOS, or `requires = listOf(ConsentCategory.ADVERTISING)` on Android) are dropped until that category is granted. +## PII scrubbing + +Same options as the web tracker's `pii` (#482). Scrubbing runs **when you call +`track()`, before the event is queued**, so a raw value never reaches the +on-device queue (NSUserDefaults / SharedPreferences) or the network. + +- **Denylist:** remove matching keys. +- **Classify:** a per-key action. The first rule for a key wins. + - `DROP`: remove the key. + - `REDACT`: replace the value with `""`. + - `TOKENIZE`: replace it with `t:1`, `t:2`, …, stable for the tracker's lifetime. + - `HASH`: `h:` plus FNV-1a 32-bit hex. It's identical to the web tracker, so + the same value hashes the same on both platforms. **Not** cryptographic. + +Keys match exactly (case-sensitive) at any depth, including inside lists. + +```kotlin +// Android +val score = newScoreTracker( + context, config, + pii = PiiOptions.Classify(listOf( + PiiRule("email", PiiAction.HASH), // joinable across web + app + PiiRule("phone", PiiAction.REDACT), + PiiRule("memberNumber", PiiAction.TOKENIZE), + PiiRule("dateOfBirth", PiiAction.DROP), + )), +) +score.track("profile_updated", mapOf("email" to "fan@example.com", "favouriteTeam" to "home-club")) +// queued and sent as {"email":"h:141e28bb","favouriteTeam":"home-club"} +``` +```swift +// iOS +let score = ScoreClient( + publishableKey: "pk_…", siteId: "app.example.com-1a2b3c", + endpoint: "https://api.example.com", sdkVersion: "0.2.0", + pii: PiiOptions.Classify(rules: [ + PiiRule(field: "email", action: .hash), + PiiRule(field: "phone", action: .redact), + PiiRule(field: "memberNumber", action: .tokenize), + PiiRule(field: "dateOfBirth", action: .drop), + ]) +) +``` + +To remove keys without per-field actions, use +`PiiOptions.Denylist(listOf("email", "phone"))`. + ## Auth0 (JWT) Mirrors the web tracker (#109): supply a token provider — it's consulted before diff --git a/Package.swift b/Package.swift index e36c08c..53568a9 100644 --- a/Package.swift +++ b/Package.swift @@ -1,10 +1,11 @@ // swift-tools-version:5.9 import PackageDescription -// Public SPM package for the Score iOS SDK. `ScoreTracker` (Swift facade, -// ScoreClient) wraps the binary `ScoreTrackerKit` XCFramework, hosted as a -// public release asset on this repo. Consume via git URL: -// .package(url: "https://git.3halves-labs.com/3HL-SCORE/public-sdks-score.git", from: "0.1.0") +// SPM package for the iOS SDK (#178/#188). The `ScoreTracker` Swift facade +// (ScoreClient) wraps the binary `ScoreTrackerKit` XCFramework, hosted as a +// **public** Gitea release asset on `3HL-SCORE/public-sdks-score` (#284). +// Build/refresh via `scripts/release.sh` (rebuild the XCFramework, re-zip, +// recompute the checksum, re-upload to the public release). let package = Package( name: "ScoreTracker", platforms: [.iOS(.v14)], @@ -14,8 +15,8 @@ let package = Package( targets: [ .binaryTarget( name: "ScoreTrackerKit", - url: "https://git.3halves-labs.com/3HL-SCORE/public-sdks-score/releases/download/mobile-v0.1.0/ScoreTrackerKit.xcframework.zip", - checksum: "ae4b4c9b2a7e386d58c1c1144d15c3b4e5f457099ac2677e7d22894a7e4ab3e5" + url: "https://git.3halves-labs.com/3HL-SCORE/public-sdks-score/releases/download/mobile-v0.2.0/ScoreTrackerKit.xcframework.zip", + checksum: "328f8e36fc0c71d5b82bd4cc79d3d12b4520dea2db2358c73922348d83144b47" ), .target( name: "ScoreTracker", diff --git a/README.md b/README.md index 0bef354..25b471d 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,24 @@ + # Score mobile SDKs (public distribution) -Public, auth-free distribution of the **Score mobile tracker** SDKs for iOS and Android — the mobile counterpart of the web `@3halves-labs/score-tracker`. +Public, auth-free distribution of the **Score mobile tracker** SDKs for iOS and Android — the mobile counterpart of the web `@3halves-labs/score-tracker`. Lightweight app analytics / fan-engagement tracking with a durable offline queue, consent gating, and Auth0 JWT support. -## Install — 0.1.0 (no token / login required) +> This repo hosts the **consumable artifacts** (release assets + package manifests). The SDK source lives in 3 Halves Labs' private platform repo. -### iOS — Swift Package Manager (git URL) +## Install — 0.2.0 (no token / login required) + +### iOS — Swift Package Manager +```bash +swift package-registry set https://git.3halves-labs.com/api/packages/3HL-SCORE/swift +``` ```swift // Package.swift -dependencies: [ .package(url: "https://git.3halves-labs.com/3HL-SCORE/public-sdks-score.git", from: "0.1.0") ] -// target: .product(name: "ScoreTracker", package: "public-sdks-score") +dependencies: [ .package(id: "threehalveslabs.ScoreTracker", from: "0.2.0") ] +// target: .product(name: "ScoreTracker", package: "ScoreTracker") ``` ### iOS — CocoaPods @@ -18,16 +28,22 @@ pod 'ScoreTracker', ``` ### iOS — direct XCFramework -Download `ScoreTrackerKit.xcframework.zip` from the [`mobile-v0.1.0` release](https://git.3halves-labs.com/3HL-SCORE/public-sdks-score/releases) and drag it into Xcode. +Download `ScoreTrackerKit.xcframework.zip` from the [`mobile-v0.2.0` release](https://git.3halves-labs.com/3HL-SCORE/public-sdks-score/releases) and drag it into Xcode. ### Android — Gradle ```kotlin -repositories { maven { url = uri("https://git.3halves-labs.com/api/packages/3HL-SCORE/maven") } } // public -dependencies { implementation("com.threehalveslabs.scoretracker:shared-android:0.1.0") } +repositories { + maven { url = uri("https://git.3halves-labs.com/api/packages/3HL-SCORE/maven") } // public — no credentials +} +dependencies { + implementation("com.threehalveslabs.scoretracker:shared-android:0.2.0") +} ``` -## Docs +## Quickstart & API -Full integration guide (init, consent, Auth0, offline/flush, event catalogue): **[`INTEGRATION.md`](INTEGRATION.md)**. +See the full integration guide (init, consent, Auth0, offline/flush, event catalogue): **`INTEGRATION.md`** in this repo (published alongside each release). -SDK source lives in 3 Halves Labs' platform repo; this repo hosts the consumable artifacts. Versions tagged `0.1.0` (SPM) / `mobile-v0.1.0` (release asset). +## Support + +Issues / integration questions: contact 3 Halves Labs. Versions are tagged `mobile-v`. diff --git a/ScoreTracker.podspec b/ScoreTracker.podspec index 8e32d05..cb69c01 100644 --- a/ScoreTracker.podspec +++ b/ScoreTracker.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'ScoreTracker' - s.version = '0.1.0' + s.version = '0.2.0' s.summary = 'Score mobile tracker (iOS) — KMP shared core + Swift facade.' s.description = <<-DESC Lightweight app analytics / fan-engagement tracker. Emits the same event @@ -13,8 +13,8 @@ Pod::Spec.new do |s| s.platform = :ios, '14.0' s.swift_version = '5.9' - # Release attaches the zipped XCFramework; point :http at that asset. - s.source = { :http => 'https://git.3halves-labs.com/3HL-SCORE/public-sdks-score/releases/download/mobile-v0.1.0/ScoreTrackerKit.xcframework.zip' } + # Release attaches the zipped XCFramework as a PUBLIC asset on public-sdks-score (#284). + s.source = { :http => 'https://git.3halves-labs.com/3HL-SCORE/public-sdks-score/releases/download/mobile-v0.2.0/ScoreTrackerKit.xcframework.zip' } s.vendored_frameworks = 'ScoreTrackerKit.xcframework' s.source_files = 'Sources/ScoreTracker/**/*.swift' diff --git a/Sources/ScoreTracker/ScoreClient.swift b/Sources/ScoreTracker/ScoreClient.swift index 2ea6b5c..653dc91 100644 --- a/Sources/ScoreTracker/ScoreClient.swift +++ b/Sources/ScoreTracker/ScoreClient.swift @@ -13,6 +13,7 @@ public final class ScoreClient { /// - Parameters: /// - endpoint: middleware base URL; events POST to `/api/tracking/website`. /// - initialConsent: defaults to "none" — nothing fires until `setConsent(...)`. + /// - pii: field-level PII handling (#482), scrubbed before events are queued on the device. public init( publishableKey: String, siteId: String, @@ -22,7 +23,8 @@ public final class ScoreClient { os: String? = nil, device: String? = nil, locale: String? = nil, - initialConsent: ConsentState = ConsentState.companion.NONE + initialConsent: ConsentState = ConsentState.companion.NONE, + pii: PiiOptions? = nil ) { let config = TrackerConfig( publishableKey: publishableKey, @@ -31,7 +33,7 @@ public final class ScoreClient { sdk: SdkInfo(name: "@3halves-labs/score-tracker-ios", version: sdkVersion), device: DeviceDescriptor(os: os, device: device, appVersion: appVersion, locale: locale) ) - tracker = IosPlatformKt.doNewScoreTracker(config: config, initialConsent: initialConsent) + tracker = IosPlatformKt.doNewScoreTracker(config: config, initialConsent: initialConsent, pii: pii) } // MARK: - Consent (wire to your CMP / OneTrust)