package ironlicensing import ( "context" "errors" "fmt" "sync" ) var ( ErrNotInitialized = errors.New("ironlicensing: client not initialized") ErrPublicKeyRequired = errors.New("ironlicensing: public key is required") ErrProductSlugRequired = errors.New("ironlicensing: product slug is required") ) type LicenseRequiredError struct { Feature string } func (e *LicenseRequiredError) Error() string { return fmt.Sprintf("Feature '%s' requires a valid license", e.Feature) } type Client struct { options Options transport *Transport currentLicense *License licenseKey string mu sync.RWMutex } var globalClient *Client var globalMu sync.RWMutex func NewClient(options Options) (*Client, error) { if options.PublicKey == "" { return nil, ErrPublicKeyRequired } if options.ProductSlug == "" { return nil, ErrProductSlugRequired } opts := options.WithDefaults() c := &Client{ options: opts, transport: NewTransport(opts.APIBaseURL, opts.PublicKey, opts.ProductSlug, opts.HTTPTimeout, opts.Debug), } if opts.Debug { fmt.Println("[IronLicensing] Client initialized") } return c, nil } func Init(publicKey, productSlug string, opts ...func(*Options)) error { options := DefaultOptions() options.PublicKey = publicKey options.ProductSlug = productSlug for _, opt := range opts { opt(&options) } client, err := NewClient(options) if err != nil { return err } globalMu.Lock() globalClient = client globalMu.Unlock() return nil } func WithDebug(debug bool) func(*Options) { return func(o *Options) { o.Debug = debug } } func getGlobalClient() (*Client, error) { globalMu.RLock() defer globalMu.RUnlock() if globalClient == nil { return nil, ErrNotInitialized } return globalClient, nil } func (c *Client) Validate(licenseKey string) LicenseResult { return c.ValidateContext(context.Background(), licenseKey) } func (c *Client) ValidateContext(ctx context.Context, licenseKey string) LicenseResult { result := c.transport.Validate(ctx, licenseKey) if result.Valid && result.License != nil { c.mu.Lock() c.currentLicense = result.License c.licenseKey = licenseKey c.mu.Unlock() } return result } func (c *Client) Activate(licenseKey string) LicenseResult { return c.ActivateContext(context.Background(), licenseKey, "") } func (c *Client) ActivateContext(ctx context.Context, licenseKey, machineName string) LicenseResult { result := c.transport.Activate(ctx, licenseKey, machineName) if result.Valid && result.License != nil { c.mu.Lock() c.currentLicense = result.License c.licenseKey = licenseKey c.mu.Unlock() } return result } func (c *Client) Deactivate() bool { return c.DeactivateContext(context.Background()) } func (c *Client) DeactivateContext(ctx context.Context) bool { c.mu.RLock() key := c.licenseKey c.mu.RUnlock() if key == "" { return false } if c.transport.Deactivate(ctx, key) { c.mu.Lock() c.currentLicense = nil c.licenseKey = "" c.mu.Unlock() return true } return false } func (c *Client) StartTrial(email string) LicenseResult { return c.StartTrialContext(context.Background(), email) } func (c *Client) StartTrialContext(ctx context.Context, email string) LicenseResult { result := c.transport.StartTrial(ctx, email) if result.Valid && result.License != nil { c.mu.Lock() c.currentLicense = result.License c.licenseKey = result.License.Key c.mu.Unlock() } return result } func (c *Client) HasFeature(featureKey string) bool { c.mu.RLock() defer c.mu.RUnlock() if c.currentLicense == nil { return false } for _, f := range c.currentLicense.Features { if f.Key == featureKey && f.Enabled { return true } } return false } func (c *Client) RequireFeature(featureKey string) error { if !c.HasFeature(featureKey) { return &LicenseRequiredError{Feature: featureKey} } return nil } func (c *Client) GetFeature(featureKey string) *Feature { c.mu.RLock() defer c.mu.RUnlock() if c.currentLicense == nil { return nil } for _, f := range c.currentLicense.Features { if f.Key == featureKey { return &f } } return nil } func (c *Client) License() *License { c.mu.RLock() defer c.mu.RUnlock() return c.currentLicense } func (c *Client) Status() LicenseStatus { c.mu.RLock() defer c.mu.RUnlock() if c.currentLicense != nil { return c.currentLicense.Status } return StatusNotActivated } func (c *Client) IsLicensed() bool { c.mu.RLock() defer c.mu.RUnlock() return c.currentLicense != nil && (c.currentLicense.Status == StatusValid || c.currentLicense.Status == StatusTrial) } func (c *Client) IsTrial() bool { c.mu.RLock() defer c.mu.RUnlock() return c.currentLicense != nil && (c.currentLicense.Status == StatusTrial || c.currentLicense.Type == TypeTrial) } func (c *Client) GetTiers() []ProductTier { return c.transport.GetTiers(context.Background()) } func (c *Client) StartPurchase(tierID, email string) CheckoutResult { return c.transport.StartCheckout(context.Background(), tierID, email) } // Global functions func Validate(licenseKey string) LicenseResult { c, err := getGlobalClient() if err != nil { return LicenseResult{Valid: false, Error: err.Error()} } return c.Validate(licenseKey) } func Activate(licenseKey string) LicenseResult { c, err := getGlobalClient() if err != nil { return LicenseResult{Valid: false, Error: err.Error()} } return c.Activate(licenseKey) } func Deactivate() bool { c, err := getGlobalClient() if err != nil { return false } return c.Deactivate() } func StartTrial(email string) LicenseResult { c, err := getGlobalClient() if err != nil { return LicenseResult{Valid: false, Error: err.Error()} } return c.StartTrial(email) } func HasFeature(featureKey string) bool { c, err := getGlobalClient() if err != nil { return false } return c.HasFeature(featureKey) } func RequireFeature(featureKey string) error { c, err := getGlobalClient() if err != nil { return err } return c.RequireFeature(featureKey) }