Keylight.manager
Build your LicenseManager
Section titled “Build your LicenseManager”import SwiftUIimport KeylightSDK
@MainActorenum Licensing { static let manager = try! Keylight.manager( sdkKey: "sdk_live_...", // from your Keylight dashboard tenantId: "acme", // assigned at signup productId: "widget", // set in the dashboard keyPrefix: "ACME", // 4-char prefix from the dashboard trustedPublicKeyBase64: "...", // shown once in the dashboard trialDurationDays: 14, branding: BrandingConfig( appName: "Widget", purchaseURL: URL(string: "https://acme.example.com/buy")!, supportEmail: "support@acme.example.com", tintColor: .orange ) )}The factory builds the KeylightConfiguration, the KeylightProvider (with the Keylight origin hardcoded), and the @MainActor LicenseManager in one call. The sdkKey authenticates your app against your Keylight account - rotate it from the dashboard if it’s ever compromised.
Access-modifier hygiene
Section titled “Access-modifier hygiene”Keep your Licensing.manager reference internal (the default) or private within your app module. Do not re-export it as public:
// ✅ good - only your app's own code can touch it@MainActorenum Licensing { static let manager = try! Keylight.manager(...)}
// ❌ bad - anything linking your module can call activate/deactivatepublic let sharedLicenseManager = try! Keylight.manager(...)This matters when your app loads plugins, embeds scripting, or is itself a framework other modules link against. A public manager lets any linked code call manager.activate(key:) with a forged key or swap state to .licensed. For a standalone app with no plugin surface it is mostly theoretical, but the rule is free to follow.
Gate your app
Section titled “Gate your app”@mainstruct WidgetApp: App { @StateObject private var manager = Licensing.manager @Environment(\.scenePhase) private var scenePhase
var body: some Scene { WindowGroup { ContentView() .environmentObject(manager) .task { await manager.checkOnLaunch() } .onChange(of: scenePhase) { phase in if phase == .active { Task { await manager.refreshIfNeeded() } } } } }}
struct ContentView: View { @EnvironmentObject var manager: LicenseManager
var body: some View { if manager.isEntitled { ProFeaturesView() } else { UpgradePromptView() } }}Build your own upgrade/activation UI, or drop in the bundled LicensePromptView(manager:).
Gating features
Section titled “Gating features”Anywhere in your view tree, read manager.isEntitled (which is true for both .trial and .licensed states) to decide whether to expose paid functionality. For state-specific UI (e.g. showing remaining trial days), switch on manager.state directly.
Reacting to activation and deactivation
Section titled “Reacting to activation and deactivation”LicenseManager posts Notification.Name.keylightLicenseDidChange after any state transition. Subscribe if you need to invalidate caches or refresh remote data when entitlement changes.
NotificationCenter.default.addObserver( forName: .keylightLicenseDidChange, object: nil, queue: .main) { _ in // react to entitlement change}Activation and deactivation are driven by LicensePromptView, which calls manager.activate(key:) and manager.deactivate() under the hood. You can also call them directly if you build your own paywall UI.