Skip to Content
macOS (Swift)Stored data

Where the macOS SDK stores data

The SDK stores four small values per product, in the Keychain, and mirrors them in files in case the keychain is unavailable or loses them.

ValueWhat it isWritten
licenseKeyThe key the customer activated.When setLicenseKey(_:) succeeds.
licenseTokenThe last license token.At each successful validation. Deleted when LicenseManager refuses the license.
installIdThe random part of the fingerprint.The first time the fingerprint is computed. Never changed afterwards.
lastSeenThe latest time seen by the clock check, lightly obscured in base64.At each clock check.

Nothing else is stored: no customer data, no history.

The entries and files are the same as the Electron SDK’s and the Tauri SDK’s: a Swift build of your app reads what an Electron or Tauri build wrote, and the other way around. See migrating from Electron or Tauri.

The keychain

Values are generic passwords in the user’s login keychain, the one the Keychain Access app shows, like those of the Electron and Tauri SDKs. Each has:

  • the service keychainService, which defaults to your app’s bundle id, like com.acme.photo, or dev.licensemanager.sdk when the app has none. It’s the name macOS shows when it asks for permission.
  • an account made of your product id and the value’s name, like x7Kp2QaZ:licenseKey. Two apps, or two products, never read or overwrite each other’s entries.
.init(keychainService: "com.acme.photo") // default: the bundle id .init(useKeychain: false) // files only

Development builds

useKeychain defaults to false for unsigned and ad-hoc signed builds, like swift run, or Xcode’s Sign to Run Locally: their signature changes on every build, and the Keychain would ask for permission each time. They use the files only. Builds signed with a certificate, Apple Development or Developer ID, use the Keychain. Pass useKeychain: true or false to choose yourself.

When the Keychain can’t be reached, or refuses access, the SDK uses the files only, without any error. When a value is found in a file but not in the Keychain, like after keychainService changed, it’s written back to the Keychain.

Files

Each value is also written to two folders, even when the keychain works:

AppFolders
Not sandboxed~/Library/Application Support/.<namespace> and ~/Library/Caches/.<namespace>
SandboxedThe same folders, inside the app’s container: ~/Library/Containers/<bundle id>/Data/Library/…
  • <namespace> is the first 32 hex characters of the SHA-256 of dev.licensemanager.sdk:<productId>. It looks like a random id, and doesn’t mention licenses or your app.
  • Each value is in a file named after the first 24 hex characters of the SHA-256 of its name, like 3f1a…c9.dat.
  • Files are created readable by the user only (600). They aren’t encrypted.
  • Writing to one folder can fail without affecting the other.

For apps that aren’t sandboxed, these folders are outside your app’s own folders, on purpose: see uninstalling.

Reading and writing

OperationWhat happens
ReadThe keychain first. When it has no value, or fails, each folder in order. The first value found wins.
WriteThe keychain, then both folders. Each one can fail on its own: the others are still written.
DeleteThe keychain entry and both files.

None of them ever fails: storage is best effort, and a failure never crashes your app. They run on the SDK’s actor, never on the main thread.

Uninstalling

The stored data is kept when your app is moved to the Trash, on purpose: the Keychain and the folders are outside the app. Cleaner apps that delete an app’s container remove the files of a sandboxed app, but not its Keychain items. A customer who reinstalls your app is still activated, with the same fingerprint, and doesn’t take a new place on their license. A trial can’t be restarted by reinstalling either.

Resetting during development

To start over as a new customer, delete the entries of your product in the keychain, and the two folders: when only one of them is deleted, the SDK reads the values from the other.

Compute your product’s namespace:

printf 'dev.licensemanager.sdk:x7Kp2QaZ' | shasum -a 256 | cut -c1-32
for name in licenseKey licenseToken installId lastSeen; do security delete-generic-password -s com.acme.photo -a "x7Kp2QaZ:$name" # your keychainService done rm -rf ~/Library/Application\ Support/.<namespace> ~/Library/Caches/.<namespace> # Sandboxed app: rm -rf ~/Library/Containers/com.acme.photo/Data/Library/{Application\ Support,Caches}/.<namespace>

Deleting the install id changes the fingerprint: your computer becomes a new machine on the license. Use a test product, where it doesn’t matter.

Never tell customers to delete these files to fix a problem: they lose their activation, and their computer becomes a new machine, which can be refused with machine_limit_reached.

Edge cases

The Keychain asks for permission. A build signed with another certificate than the one that created an entry, like a development build after a release build, or a Swift build after an Electron build, makes the Keychain ask the customer to allow access, once. When access is denied, the SDK reads the values from the files.

The Keychain is locked or unavailable, like over SSH. The SDK uses the files.

The keychain and the files disagree, like after restoring a backup. The keychain wins when it has a value.

A read-only home folder, and no keychain. Nothing can be stored: the customer must activate on every launch, and each launch is a new machine. Very unlikely on a customer’s computer.