Skip to content
VoteFirst Docs
Dashboard

Swift

Voters

Every vote, comment and suggestion made in your app is recorded against one voter. This is where that voter comes from, how to make it somebody your app already knows, and what happens when a project stops somebody writing.

The default

Nothing to set up. Each device gets its own anonymous voter, minted the first time it needs one, and kept between launches.

It is minted by the first write, or by the first built in screen appearing, or by me(), whichever happens first. There is nothing to call before a vote.

Mints the voter if there is none
let vote = try await VoteFirst.upvote(featureID: 21519)

For an app with no accounts this is the right answer and the end of the subject.

Reading the voter

In your app
let voter = try await VoteFirst.me()
FieldWhat it is
voterIDWhat every vote and comment of theirs is recorded against, such as anon_41bdabf9... or sso_42_user-9
kind.anonymous or .sso
displayNameWhat their comments are signed with
votedFeatureIDsEvery feature on this project they have already voted for
bannedThe project has stopped them writing. They still read and vote
anonTokenThe bearer value for the anonymous voter. The package stores it for you and you do not need it

votedFeatureIDs is what a custom board draws its vote buttons from on the first render. A page of features carries hasVoted on each one as well, so you rarely need both, but the identity is one request where a whole board is several pages.

me() renews rather than replaces. Calling it on every launch keeps the same voter and their whole history.

Where the token lives

The anonymous voter is a token the device holds. The package keeps it in the keychain, as a generic password under the service com.votefirst.sdk with the project as the account, marked readable after the first unlock so a write queued on a previous launch can go out before anybody types a passcode.

It is deliberately not in user defaults. Reading user defaults is a required reason API, so storing an identifier there would oblige your app to carry a declaration for it in your own privacy manifest. See Privacy.

A build that cannot reach the keychain falls back to a file in the application support directory, readable only by its owner. That is not a workaround for a shipped app, which always has the keychain; it is what makes an unsigned debug build keep its voter between runs instead of becoming a new person on every launch.

The token is bound to one project. It is signed by the server for that project and no other, so a token taken from one board cannot be replayed against another.

Signing your own users in

If people are already signed in to your app, sign them in here too, and their votes are theirs on every device rather than tied to one phone.

In your app
try await VoteFirst.signIn(ssoToken: tokenYourServerSigned)

Your own server signs the token. It puts your user's id in sub, their name and email if you have them, and an expiry in exp, and signs it with the project's SSO secret from Share & Embed in your dashboard. The SSO quickstart has that server side in five languages, and it is the same token a web page uses.

The secret never goes in the app. Anyone holding it can sign a token claiming to be any of your users. A client that could name any user could vote as any user, which is the whole reason this is a token rather than a user id argument.

ClaimWhat it is
subRequired. Your own id for this person. The voter becomes sso_{projectID}_{sub}
expRequired. A token with no expiry is refused. On a phone these sit in a keychain for months, so one that never expires is one that never stops working if it leaks
nameOptional. What their comments are signed with
emailOptional. Kept on their voter profile, never published on the board

Two things have to be true on the VoteFirst side or every token is refused: the project owner is on a Pro plan, and the project has an SSO secret. Both are on the Share & Embed page.

The sign in is held in memory, not stored. Call signIn on every launch, in the same place you restore your own session.

A refused token throws identityInvalid. The anonymous voter this device already had is still there, so the person is left able to take part rather than stranded.

Signing out

In your app
await VoteFirst.signOut()

Drops the sign in and falls back to this device's anonymous voter, which is still there and still has its own history. Nothing is sent, and nothing already recorded moves.

Forgetting a device

In your app
await VoteFirst.forget()

Drops the anonymous token, the cached identity and anything queued to send. The next write mints a new voter with no history.

Votes already cast stay on the board under the voter that cast them. This is a local reset rather than a deletion request, and it is what to call when somebody signs out of your app entirely and hands the phone to somebody else.

It does not clear what the reader chose to hide. Choosing not to see abuse again is not part of an identity.

When a project stops somebody writing

A project owner can ban a voter. What that means here:

ReadingStill works
VotingStill works
LikingStill works
CommentingvoterBanned
SuggestingvoterBanned

me().banned reports it, and the built in screens read it: the comment box and the suggestion form are replaced with one line saying so. An app drawing its own interface should check it for the same reason, because finding out by having a write refused is a worse way to learn it.

A ban is per project and per voter. Signing in as somebody else is a different voter.

Names

An anonymous voter is given a stable generated handle, such as VelvetOwl_3069. It is the same handle every time for that voter, which is what makes a thread readable, and it says nothing about who they are.

A signed in voter is called whatever the token said, or whatever name is on their VoteFirst account if they have one.

Blocking in the built in screens works on this name, because it is the only thing the API says about who wrote a comment. Two voters can land on the same handle and a signed in voter can choose their own, so blocking hides by name rather than claiming to identify a person.

Questions this raises

Is the app's voter the same person as the website's voter? No, unless they sign in on both. An anonymous voter is whoever holds the token, your app cannot read a browser cookie, and the two are minted separately. The token is the same shape the web board uses, so an integration that carried one across would be the same voter, but nothing does that for you.

Do two of my apps share a voter? No. The keychain item belongs to the app that wrote it, so a second app of yours over the same project mints its own voter. Signing both in with SSO makes them one person.

Does the voter survive a reinstall? Maybe. The keychain item is not in the app's container and can outlive it, and the file fallback cannot. Do not build anything on either answer.

Does a person get one voter per project? Yes. Everything the package stores is filed under the project, so an app showing two boards has two anonymous voters, one per board.

How many voters can I mint? 300 a minute, in bursts of 30. It is the smallest budget your project has, because every mint is a new voter and unlimited minting is what would defeat the one vote per person index. Every call to the identity route is counted against it, minting or renewing, signed in or anonymous, so an app calling me() on every launch is spending from it too. See Budgets.

Next