keygrant library

KeyGrant licensing for Flutter apps: offline-verified leases, trials, licence files, metered uses and key pickup, on iOS, Android, Windows, macOS and Linux.

Classes

Activated
The key is activated on this device.
ActivateFailed
The key was not activated, and why.
ActivateResult
What activate() did.
BaseFigure
A meter's figures as a lease signs them (met) or an answer gives them, without the time of the answer: what was left, the uses it holds, the offline allowance each device may spend, and whether it comes back each billing period.
DeactivateResult
What deactivate() did about the seat. The local licence is dropped either way; released says whether the device's seat is free: true when KeyGrant confirmed the release (a 200), or when this device held no seat to release; false when it could not be asked, or answered anything else.
DesktopFingerprinter
The desktop default: the machine id (readMachineId) hashed, the host name's when the machine has no id to read; the host name's always offered as an alternate. get waits as long as an activation does and throws when the id still could not be read.
DeviceIdentity
What a fingerprinter says of this device: its fingerprint (null when it cannot be told this time), what it hashes (kind, null when unknown), and the other fingerprints this device answers to.
Entitlement
One entitlement's value: a flag, a number (a double, as JSON reads one) or a text. Compared by content: NumberEntitlement(5) equals NumberEntitlement(5.0).
FileStorage
JSON file storage. Read: only a MISSING file is no state (its newest whole temp file read first); any other failure is tried again for about a second and then throws; a file that does not parse is set aside as <file>.<ms>.corrupt (the newest three kept) and an empty state written in its place. Written atomically: a temp file beside it, flushed, renamed over it (tried again when busy), the folder flushed after on POSIX. Each save stamps rev one higher and checks just before the rename that the file's rev is the one it read, merging again onto a newer state up to five times.
Fingerprinter
A fingerprinter: what identifies this device. get alone says nothing of what the fingerprint hashes, so its seats are never held to their device; implement identify and report DeviceKind.machine for a stable machine id that should be.
FlagEntitlement
A flag: granted when true.
HttpAdapter
POST JSON. Answers every status (a 4xx and a 5xx included) rather than throwing; throws only when there was no answer (no network, a timeout). timeout is how long the call may take: an adapter that ignores it is still held to it by the SDK.
HttpResponse
What a POST came back with: its HTTP status, and its body as JSON (null when it had none, or none that parses).
ImportLicenseFileResult
What importLicenseFile() did.
KeychainTrialStash
A trial's start kept in the iOS Keychain (service dev.keygrant.trial, account the product slug), which outlives the app, through the plugin.
KeyGrantLicense
Client-side licensing for an app. Verifies signed leases OFFLINE and only contacts KeyGrant to activate, to renew a lease nearing its end or past it, to tell it of a newer major, to spend a metered use or report the uses this device spent with no answer, or (at most once a day, in the background) to report a running trial's launch: so an outage, no network, a 5xx, a 429, an ambiguous 4xx or an unusable answer never locks out a paying customer while their lease is valid. Only an explicit verdict from the server, or the lease's own end, does.
KeyGrantPlugin
Registers the plugin's Dart side: Flutter calls registerWith for every platform when the app starts (the plugin's dartPluginClass). An isolate the app starts itself calls DartPluginRegistrant.ensureInitialized() first.
LicenseAdapters
Adapter overrides; any left null uses the default.
LicenseClock
The device clock, in milliseconds since the epoch.
LicenseConfig
How a licence is set up.
LicensedTo
Who a licence file is licensed to: a name, an email address, or both.
LicenseFileActivationFailed
A key file whose key's activation failed, as activate() answers it.
LicenseFileImported
An offline file is stored and licenses the app from now on; a key file's key is activated. licensedTo is who the file names.
LicenseFileRefused
The file was not taken, and why; nothing was stored.
LicenseStatus
The licence's status. Compared by content: an app comparing two statuses sees no change from a lease that grants nothing.
MeterFigure
A meter's figures as the newest use answer gave them; at is the server's time (ms).
MeterStatus
One meter of a key as status().meters shows it: what is left net of this device's unsynced spends (never below 0), the uses it holds, its offline allowance, the uses this device spent that KeyGrant has not heard of, and whether it comes back each billing period.
MobileFingerprinter
The phone and tablet default (4.1): iOS's Keychain install id (raw machine-id|ios:<id>) and Android's ANDROID_ID (raw machine-id|android:<id>, lower case), kind machine, no alternate. An Android device with no usable ANDROID_ID (none, or the one old builds share) gets an install id of the SDK's own in the app's private storage, raw install-id|android:<uuid>, of no kind, so its seats are never held to the device. Anything that fails is no fingerprint this time.
NoRegistryStash
The stash that keeps nothing: the default where no platform stash applies.
NumberEntitlement
A number: granted when not 0 (a negative counts: -1 for unlimited).
PackageHttp
HTTP over package:http, every call bounded: abandoned (the request aborted) and thrown once timeout has passed, which the SDK treats as no network. Without a bound a network that accepts a connection and never answers (a captive portal, a dead proxy) would hold a check-in for minutes.
PickedKey
One key a pickup handed back: enough for the app to let the customer pick one (its model, what it grants, its versions, its seats), never shown as a key. Activate it with activate(picked.key, email: ..., code: picked.activationCode), or let restoreByEmail choose and activate one.
PickedSale
The sale of several keys a picked key is one of.
PickedVersions
The majors a picked key covers; maxMajor null for no top.
PickUpCodeSent
The code is on its way when the address holds a key of the product; the answer is the same when it does not.
PickUpKeysFailed
The confirm was refused; retryAfter (seconds) when the server says.
PickUpKeysFound
The address's live keys of the product, for the customer to pick one by its model (never shown as a key).
PickUpKeysResult
What confirmPickUp(email, code) did.
PickUpSendFailed
The ask was refused; retryAfter (seconds) when the server says how long to wait.
PickUpSendResult
What pickUp(email) did.
RegistryStash
A trial's start kept where it outlives the app (the Windows registry, the iOS Keychain), so a reinstall does not restart a trial. Either call may throw; neither ever costs the customer anything.
Restored
A key of the address is activated on this device.
RestoreFailed
Nothing was activated, and why; retryAfter (seconds) when a pickup refusal says.
RestoreResult
What restoreByEmail(email, code) did.
SpendEntry
A spend this device made that KeyGrant has not confirmed (the journal).
StorageAdapter
Where the stored state lives.
StoredState
The licence state a device keeps between runs, one per product. Read-only: the SDK saves a new one. A field this SDK does not know (a newer SDK's) is kept as it was and written back.
SystemClock
The system clock.
TextEntitlement
A text: granted when not empty.
UseRefused
Not spent, and why; left for used-up and offline-limit.
UseResult
What use(meter, n) did.
UseSpent
Spent. left is what is left of the meter now, net of this device's unsynced spends; offline is true when KeyGrant could not be asked and the spend came from the offline allowance.

Enums

DeviceKind
What a fingerprint hashes: the operating system's machine id, or the host name.
LicenseError
Why activate() did not activate.
LicenseFileError
Why a licence file was not taken (importLicenseFile).
LicenseFileKind
The kinds of licence file: one that carries a key to activate, and one that licenses offline for good.
LicenseReason
Why a licence does not license this build. upgrade: the build is NEWER than the key covers. outdated: OLDER than its lowest version. device: activated on another device. tampered: the lease is not this licence's at all. unknown-key: signed by a key this build does not carry. storage: the state could not be read just now. revoked-file: the stored offline licence file is one this build lists as revoked.
LicenseSource
Where a status comes from when it is not a key's lease or a trial.
LicenseState
What status() says of the licence. unknown (reason storage): the state could not be read just now: keep running, and ask again shortly.
PickupError
Why a pickup call (section 13) did not do what it asked.
Refusal
A definitive refusal from the server, as the stored state keeps it.
RestoreError
Why restoreByEmail() did not activate a key: a pickup's error, 13.4's no-key (upgrade and outdated when keys were found but none covers this build), or an activation's.
SpendKind
What kind of entry the journal holds: spends made with no request sent (one entry for each meter, growing), one spend whose own request got no answer, or one such spend refused after (reported so the server takes it back).
UseError
Why use() did not spend (12.9).

Constants

defaultHttpTimeout → const Duration
How long a request may take before it is abandoned, unless the call or the configuration says otherwise.
licenseFileExtension → const String
The file's extension, for an app's open dialog and drop filter.
licenseFileMediaType → const String
The media type of a licence file attached to an email.
maxLicenseFileBytes → const int
The most a licence file may be, in bytes.

Functions

grants(Map<String, Entitlement>? entitlements, String code) → bool
Whether entitlements grant code: a flag that is on, a number that is not zero, or a text that is not empty. Absent, off, zero or empty is not granted. The code is matched exactly, case included.
parseJwkSet(String json) → List<Map<String, Object?>>
The keys of a key set given as JSON text, as /pubkey answers it ({"keys":[...]}, or that whole answer), a list of JWKs, or one JWK, for LicenseConfig.publicJwks. An object whose keys member is not a list is refused, never read as one JWK. Throws a LicenseConfigError for text that is none of these.

Typedefs

StatePatch = Map<String, Object?>
A change to the stored state: each member a field's new value, null to clear it; a field the patch does not name is left as it is.

Exceptions / Errors

LicenseCallUnfinished
What deactivate(), importLicenseFile() and removeLicenseFile() throw behind a call on this licence that has not finished: the customer's own action, which the app asks them to try again.
LicenseConfigError
A configuration the SDK cannot run with (a public key that is not one, both key members given, a denylist that is not a list of texts): thrown when the licence is made, so it is found the first time the app runs.
MissingEntitlement
What KeyGrantLicense.require throws when the licence does not grant an entitlement: it names it as entitlement.