Skip to content

feat: ios sdk (swift package) for the events collector - #18

Open
yuvrajjsingh0 wants to merge 3 commits into
sudoquery-monorepo-implfrom
feat/ios-sdk
Open

yuvrajjsingh0 wants to merge 3 commits into
sudoquery-monorepo-implfrom
feat/ios-sdk

Conversation

@yuvrajjsingh0

@yuvrajjsingh0 yuvrajjsingh0 commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Adds clients/ios, the Swift counterpart of clients/android (#17) and clients/typescript: a Swift package for iOS 13+ with an Objective-C wrapper. It is a standalone SwiftPM package, outside the Cargo workspace.

SudoQuery.initialize(try SudoQueryConfig(orgId: orgId, workspaceId: workspaceId, endpoint: "https://…/v1/events/batch"))
SudoQuery.track("button_click", properties: ["button_id": "submit"])

What's in it

  • API mirroring the Android SDK: initialize, track, setUser / removeUser / user, super properties, flush and reset(). Every method is thread-safe and never throws or traps in the host app. Objective-C apps use SQSudoQuery / SQSudoQueryConfig, and an invalid config comes back as an NSError. The project id is workspaceId, as the dashboard's switcher calls it, and it goes out as x-workspace-id and workspace_id.
  • One-line event type swap: clients/ios/Sources/SudoQuery/Event.swift holds every wire type. EventV1 matches CollectorEvent field for field and in the same key order, nulls included, except that it has workspace_id where the collector has project_id. Its bytes match the Android golden files apart from "workspace_id":"ios-demo1" (Android: "project_id":"android-demo1") and "source":"ios". typealias CanonicalEvent = EventV1 is the one line that picks the event type.
  • Ported from hyper-sdk-ios (HPJPLogPusher, HPJPLogsManager, Airborne's AJPNetworkClient and AJPFileUtil): one NDJSON file per batch, committed by atomic rename and replayed on launch, and one shared URLSession. Bugs fixed along the way:
    • A transport error is reported as an error, not as an empty URLResponse(), which HPJPLogPusher then read as an HTTP response (a likely crash when offline).
    • Any 2xx counts as sent and 400/413/422 drop the batch. Other failures keep it and back off exponentially (5 s up to 5 min, honours Retry-After), instead of deleting it or retrying back to back.
    • A batch is visible to the uploader only once it is fully written. Files are ordered by sequence number, the queue is capped at 10 MiB and 7 days, and nothing is wiped on SDK upgrade.
    • No file I/O on the main thread, and DispatchSource timers instead of NSTimers that pause while the user scrolls.
    • Storage is in Application Support, excluded from backup, with completeUntilFirstUserAuthentication, instead of the backed-up Library/.
    • Connections are reused, cookies and caches are off, and redirects are refused.
  • iOS specifics:
    • Going to the background commits and uploads inside a background task, and tries once even while backing off.
    • Before the first unlock after a reboot, the stored identity can't be read; it is never overwritten, and it is used once the device is unlocked.
    • A privacy manifest is included. No required-reason APIs are used.
  • No Swift Concurrency runtime: GCD and locks only, so iOS 13–14 apps, Objective-C-only ones included, don't need to ship the back-deployed runtime. CI checks the object files for it.
  • Sample app: clients/ios/Sample, SwiftUI with the Android sample's buttons. Its ObjCSmokeTest.m calls every Objective-C method, so building the sample checks the generated header.
  • CI: ios-client.yml runs the tests on macOS and on an iOS simulator, checks the SDK has no trapping calls (try!, as!, fatalError, precondition) and no Swift Concurrency symbols, and builds the sample. There is no release workflow yet.

Verification

  • Unit tests: 107 on macOS and 113 on the iOS 26 simulator, which adds the UIKit lifecycle tests. They cover:
    • the golden wire format and the type swap;
    • the queue, upload policy and backoff;
    • identity, including an unreadable identity file;
    • JSON conversion, including NSNumber vs Bool bridging;
    • the Objective-C bridge;
    • the facade, end to end through a loopback HTTP server.
  • Local collector, before the switch to x-workspace-id / workspace_id: the opt-in LiveCollectorTests passed from macOS and from the simulator. Events reached Kafka with source: "ios", the user id, the time zone and the server-side IP.
  • Simulator, sample app, also before the switch:
    • The Swift and Objective-C smoke tests were delivered.
    • Events tracked without a flush went out when the app went to the background, inside the SDK's background task.
    • Events landed in the project's ClickHouse tables and OpenSearch data stream next to the Android ones.
  • Review pass fixes:
    • an identity overwrite before the first unlock;
    • an Objective-C headers crash on non-string values;
    • a total upload timeout too short for big batches on slow links;
    • two smaller races.

Before publishing

  • Collector: the SDK sends x-workspace-id and workspace_id, but the collector still requires x-project-id and project_id. It answers 400 without them, and the SDK drops a batch that gets a 400, so the collector has to accept the new names before this ships.
  • SwiftPM: it reads Package.swift only at a repository root, and the v* tags here belong to npm. Publishing needs either a mirror repo fed by git subtree split --prefix=clients/ios, or a root manifest with tags that don't collide. Until then, apps add clients/ios as a local package.
  • License: the same question as feat: android sdk (kotlin) for the events collector #17. ISC would match npm and Android, but the repo has no LICENSE file.

Adds clients/ios, the Swift counterpart of clients/android and
clients/typescript: a Swift package for iOS 13+ with an Objective-C
wrapper. Apps call SudoQuery.initialize/track/setUser/flush; events are
persisted to disk, batched and posted to /v1/events/batch.

- Event.swift holds every wire type; `typealias CanonicalEvent = EventV1`
  is the one line that picks the event type the SDK sends. The bytes match
  the Android golden files apart from `"source":"ios"`.
- The network, batching and file layers are ported from hyper-sdk-ios
  (HPJPLogPusher, HPJPLogsManager, Airborne's AJPNetworkClient and
  AJPFileUtil), with its bugs fixed: a transport error is no longer turned
  into an empty URLResponse(), any 2xx counts as sent, 400/413/422 drop the
  batch, other failures back off exponentially instead of deleting the
  batch, a batch is visible to the uploader only once fully written, and
  the queue has size and age limits.
- GCD and locks only, so iOS 13-14 apps need no Swift Concurrency runtime.
  Going to the background flushes inside a background task, and an
  identity that can't be read before the first unlock is never overwritten.
- CI runs the tests on macOS and on an iOS simulator and builds the
  sample, whose Objective-C file compile-checks the generated header.
  SwiftPM publishing isn't set up yet; apps add clients/ios as a local
  package.
The config option, the Objective-C initializer and property, the
blank-id error code (still code 2) and the sample's build setting now
say workspaceId, the name the dashboard's switcher copies the id as.

The collector still reads x-project-id and each event's project_id, so
the SDK keeps sending the id under those names and the wire format is
unchanged; the golden files didn't change.
The header is now x-workspace-id, and the event field workspace_id
takes project_id's place, right after org_id. The org id still goes
out as x-org-id and org_id.

The collector still requires x-project-id and project_id and answers
400 without them, which the SDK treats as final and drops the batch,
so the collector has to accept the new names before this ships.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant