Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Sources/AmoreLicensing/AmoreLicensing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,19 @@ public final class AmoreLicensing: Licensing {
/// - bundleIdentifier: The app's bundle identifier. Defaults to `Bundle.main.bundleIdentifier`.
/// - configuration: The licensing configuration. Defaults to ``LicensingConfiguration/default``.
/// - server: The license server to use. Defaults to the Amore server.
/// - tokenStore: A custom store for persisting the license token. Defaults to a ``FileTokenStore`` in Application Support. Provide a custom ``TokenStore`` to store the token elsewhere.
public init(
publicKey: String,
bundleIdentifier: String? = nil,
configuration: LicensingConfiguration = .default,
server: LicenseServer? = nil,
tokenStore: (any TokenStore)? = nil
) throws {
let bundleIdentifier = bundleIdentifier ?? Bundle.main.bundleIdentifier ?? publicKey
self.configuration = configuration
self.publicKey = try EdDSA.PublicKey(x: publicKey, curve: .ed25519)
self.bundleIdentifier = bundleIdentifier
self.tokenStore = FileTokenStore(bundleIdentifier: bundleIdentifier)
self.tokenStore = tokenStore ?? FileTokenStore(bundleIdentifier: bundleIdentifier)
self.hardwareIdentifier = MacHardwareIdentifier()
self.licenseClient = HTTPLicenseClient(server: server ?? .amore(for: bundleIdentifier))
if shouldAutoValidate {
Expand Down
5 changes: 5 additions & 0 deletions Sources/AmoreLicensing/Documentation.docc/Index.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ AmoreLicensing provides an `@Observable` class that manages the full license lif
- ``ValidationFrequency``
- ``GracePeriod``

### Token Storage

- ``TokenStore``
- ``FileTokenStore``

### Errors

- ``AmoreError``
Expand Down
16 changes: 11 additions & 5 deletions Sources/AmoreLicensing/TokenStore/FileTokenStore.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import Foundation

struct FileTokenStore: TokenStore {
/// A ``TokenStore`` that persists the license token as a file on disk.
///
/// This is the default store used by ``AmoreLicensing`` when no custom store is provided. It writes
/// to the app's Application Support directory.
public struct FileTokenStore: TokenStore {
private let fileURL: URL

static let fileName = "license.jwt"
Expand All @@ -10,11 +14,13 @@ struct FileTokenStore: TokenStore {
self.fileURL = appSupport.appendingPathComponent(bundleIdentifier).appendingPathComponent(Self.fileName)
}

init(directory: URL) {
/// Creates a store that persists the token in the given directory.
/// - Parameter directory: The directory in which to read and write the token file.
public init(directory: URL) {
self.fileURL = directory.appendingPathComponent(Self.fileName)
}

func store(_ token: String) throws(TokenStoreError) {
public func store(_ token: String) throws(TokenStoreError) {
let directory = fileURL.deletingLastPathComponent()
do {
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
Expand All @@ -24,7 +30,7 @@ struct FileTokenStore: TokenStore {
}
}

func retrieve() throws(TokenStoreError) -> String? {
public func retrieve() throws(TokenStoreError) -> String? {
guard FileManager.default.fileExists(atPath: fileURL.path(percentEncoded: false)) else { return nil }
do {
let data = try Data(contentsOf: fileURL)
Expand All @@ -34,7 +40,7 @@ struct FileTokenStore: TokenStore {
}
}

func delete() throws(TokenStoreError) {
public func delete() throws(TokenStoreError) {
guard FileManager.default.fileExists(atPath: fileURL.path(percentEncoded: false)) else { return }
do {
try FileManager.default.removeItem(at: fileURL)
Expand Down
18 changes: 17 additions & 1 deletion Sources/AmoreLicensing/TokenStore/TokenStore.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
protocol TokenStore: Sendable {
/// A persistence mechanism for the signed license token.
///
/// `AmoreLicensing` uses a token store to persist the license JWT between launches so it can
/// validate offline. The default ``FileTokenStore`` writes to Application Support. Provide a custom
/// conformance to store the token elsewhere, and inject it via
/// ``AmoreLicensing/init(publicKey:bundleIdentifier:configuration:server:tokenStore:)``.
public protocol TokenStore: Sendable {
/// Persists the license token, replacing any previously stored token.
/// - Parameter token: The signed license JWT to store.
/// - Throws: ``TokenStoreError`` if the token cannot be written.
func store(_ token: String) throws(TokenStoreError)

/// Returns the stored license token, or `nil` if none is stored.
/// - Returns: The stored license JWT, or `nil` when no token has been saved.
/// - Throws: ``TokenStoreError`` if a stored token exists but cannot be read.
func retrieve() throws(TokenStoreError) -> String?

/// Removes the stored license token, if present.
/// - Throws: ``TokenStoreError`` if an existing token cannot be removed.
func delete() throws(TokenStoreError)
}
Loading