Skip to content

Resolve License Status Synchronously at Launch#5

Merged
lucasfischer merged 4 commits into
mainfrom
feat/remove-JWTKit
Jun 21, 2026
Merged

Resolve License Status Synchronously at Launch#5
lucasfischer merged 4 commits into
mainfrom
feat/remove-JWTKit

Conversation

@lucasfischer

Copy link
Copy Markdown
Contributor

What

AmoreLicensing.status is now resolved synchronously during init, so the
correct license state is available on the very first read at app launch with no
async round-trip and no transient "invalid" flash before the network validate
completes.

Why

Status couldn't be set synchronously before because verification went through
JWTKit's JWTKeyCollection, whose key configuration (add(eddsa:)) and
verify are both async. The stored token therefore couldn't be decoded until
a Task had run, leaving status momentarily wrong at launch.

The fix is to verify locally with a synchronous primitive. JWTKit can't do that,
so it's replaced with a small, purpose-built EdDSA (Ed25519) verifier backed by
swift-crypto.

How

  • New AmoreJWT target — synchronous EdDSAJWT.sign/verify plus
    base64url helpers, built on Curve25519.Signing.
  • AmoreLicensing.init now calls validateLocally() synchronously to seed
    status from the stored token, then kicks off the async server validate()
    as before. The async JWTKeyCollection, keysReady flag, and
    ensureKeysConfigured() are gone.
  • Errors: invalidSignature replaced by invalidPublicKey (bad key config)
    and invalidToken (anything unverifiable on arrival).

@otherguy

Copy link
Copy Markdown

Thanks, @lucasfischer!

This does appear to fix the main issue on my side: a valid stored token can now be decoded synchronously during AmoreLicensing init, so the first status read can be .valid before the async server validation finishes. That should let PixieCut remove its temporary local stored-token verifier once this is released.

One small request before merge: could you add a regression test for the launch initializer path that asserts a stored valid token produces .valid on the first status read, without awaiting validate()? That is the exact behavior PixieCut needs.

One nuance I noticed: expired-but-still-within-grace cached tokens seem to remain .unknown until async refresh fails and applies grace. Is that intentional, or should .gracePeriod also be resolved synchronously at launch?

@lucasfischer

Copy link
Copy Markdown
Contributor Author

Thanks for keeping me on my toes!

I added a new test that asserts that a stored valid token produces .valid on the first status read after init().

Thank you for pointing out the grace period weirdness. IMO .gracePeriod should also be resolved synchronously at launch and that's how I implemented it now. Additionally I added tests to assert this behavior.

@otherguy

Copy link
Copy Markdown

Thank you! Looks good now 🙂

@lucasfischer lucasfischer merged commit da4fca6 into main Jun 21, 2026
1 check passed
@lucasfischer lucasfischer deleted the feat/remove-JWTKit branch June 21, 2026 04:30
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.

2 participants