Swift
Privacy
What the Swift package collects, what it stores, what it sends, and what App Store Connect wants you to tick.
The manifest
VoteFirstKit ships a PrivacyInfo.xcprivacy. It declares two collected data types and nothing else.
| Data type | What it is |
|---|---|
| User ID | The anonymous voter identifier. Linked to the user. Not used for tracking. App functionality |
| Other user content | The suggestions and comments people write. Linked to the user. Not used for tracking. App functionality |
| Tracking | False |
| Tracking domains | None |
| Required reason APIs | None |
Both are marked linked to the user because the voter identifier is what a vote is recorded against, and it is what makes you already voted for this possible. Neither is marked for tracking, because nothing here follows anybody across other companies' apps or websites.
There are unit tests over the manifest itself: that tracking is false, that the required reason list is empty, that the two types are declared honestly, and that the file reaches the bundle under its own name. If somebody changes the manifest without meaning to, the tests say so.
What to tick in App Store Connect
Under App Privacy, unless your app already declares them:
- Identifiers → User ID, linked to the user, not used for tracking.
- User Content → Other User Content, linked to the user, not used for tracking.
Both for App Functionality.
That is the whole of what this package adds. If your app already collects a user id, the existing declaration covers it.
What is stored on the device
| What | Where | Why |
|---|---|---|
| The anonymous voter token | The keychain, under com.votefirst.sdk | So votes and comments survive a relaunch |
| The same token, when the keychain refuses | Application Support/VoteFirst/{project}/voter.token | So an unsigned debug build keeps its voter. 0600, complete file protection |
| Writes waiting for a connection | Application Support/VoteFirst/{project}/outbox.json | So a vote made on a train lands when the train arrives |
| Which stages this reader turned on | Application Support/VoteFirst/{project}/view.json | So a board looks the same next time |
| What this reader reported or blocked | Application Support/VoteFirst/{project}/hidden.json | So they do not see it again |
| Cached responses | Caches/VoteFirst/ | So a board that has not changed costs a round trip and no payload |
Nothing is written to user defaults, on purpose. See Required reason APIs.
What leaves the device
Every request goes to your VoteFirst host and nowhere else. There is no analytics endpoint, no crash reporter and no third party in the path.
| Headers | The embed key, the voter token, an idempotency key on a write, and a correlation id |
| Read bodies | None. Reads are GETs |
| Write bodies | Exactly what the person typed: a heading and a description, or a comment |
| Automatically | Nothing. The package makes no request you did not cause by showing a screen or making a call |
The server sees the request's network address, the way every server does. The package sends no device identifier, no advertising identifier, no model, no OS version, and no name of your app.
What the package never touches
- No
IDFA, noASIdentifierManager, no App Tracking Transparency prompt. - No contacts, photos, location, camera, microphone, calendar or health data.
- No
UserDefaults, noNSUbiquitousKeyValueStore, no iCloud sync. - No background tasks, no push notification registration, no silent wake ups.
- No dependency of any kind, so nothing else is in your binary because of this.
Required reason APIs
The list in the manifest is empty, and that is a design decision rather than an omission.
Reading user defaults is a required reason API. A package that keeps an identifier there obliges every app that carries it to declare that API in their own manifest, which is somebody else's paperwork created by our convenience. The keychain and the application support directory have no such requirement, so that is where the token and the reader's own lists live.
File timestamps, disk space and system boot time are the other three in that category, and nothing here reads any of them.
Resetting somebody
await VoteFirst.forget()
Drops this device's voter, the cached identity and anything queued to send. The next write mints a new voter with no history.
It does not clear what the reader chose to hide, because choosing not to see abuse again is not part of an identity.
It is a local reset. Votes and comments already recorded stay on the board under the voter that made them, and removing those is a request to the project owner, who can delete a voter and everything they wrote from the dashboard.
What is yours to write
The package declares what it collects. Your app's own privacy policy still has to say that your users' suggestions and comments are sent to a service you use, because that is your disclosure to make and not ours.
Apple also asks an app carrying content other people wrote to publish terms, offer a way to report content, offer a way to block a person, and moderate what is filed. The package brings the last three: reporting and blocking are on every comment and every feature, and every suggestion waits for the owner to approve it. The terms are yours, and there is a place to hang them.
VoteFirst.config.termsURL = URL(string: "https://example.com/terms") VoteFirst.config.privacyURL = URL(string: "https://example.com/privacy")