backsplash-app
Version:
An AI powered wallpaper app.
266 lines (223 loc) • 7.51 kB
text/typescript
import {
LicenseInfo,
LicenseValidationResult,
UsageStats,
FREE_WALLPAPERS_PER_DAY,
FREE_WALLPAPERS_PER_STYLE,
FREE_STYLE_SELECTION_LIMIT,
isStylePremium,
LicenseTier,
} from "@/types/license";
import { StoreService } from "./storeService";
import { getApiService } from "./apiServiceInstance";
import log from "@/logger";
export class LicenseService {
private storeService: StoreService;
private apiService: ReturnType<typeof getApiService>;
constructor(storeService: StoreService) {
this.storeService = storeService;
this.apiService = getApiService(storeService);
}
/**
* Get current license information
*/
getLicenseInfo(): LicenseInfo {
const license = this.storeService.get("license") as LicenseInfo | undefined;
if (!license) {
return {
licenseKey: null,
isValid: false,
isPremium: false,
validatedAt: 0,
plan: "free",
};
}
return license;
}
/**
* Validate a license key via the apiService (which calls FastAPI)
*/
async validateLicenseKey(licenseKey: string): Promise<LicenseValidationResult> {
try {
log.info(`[LicenseService] Validating license key via apiService: ${licenseKey.substring(0, 8)}...`);
// Set the license key first so apiService can validate it
const isValid = await this.apiService.license.setLicenseKey(licenseKey);
if (isValid) {
// Get the license info from apiService
const licenseInfo = this.apiService.license.getLicenseInfo();
// Convert to our internal format and store in new format for UI
const result: LicenseValidationResult = {
isValid: true,
isPremium: licenseInfo.isActive,
plan: licenseInfo.plan as LicenseTier,
message: "License key is valid",
purchaseDate: undefined, // Not available in current apiService format
};
// Store in new format (for UI)
const uiLicenseInfo: LicenseInfo = {
licenseKey,
isValid: true,
isPremium: licenseInfo.isActive,
validatedAt: Date.now(),
plan: licenseInfo.plan as LicenseTier,
purchaseDate: undefined,
};
this.storeService.set("license", uiLicenseInfo);
log.info("[LicenseService] License validated and stored successfully via apiService");
return result;
} else {
return {
isValid: false,
isPremium: false,
message: "Invalid license key",
plan: "free",
};
}
} catch (error) {
log.error("[LicenseService] License validation failed:", error);
return {
isValid: false,
isPremium: false,
message: "Failed to validate license key. Please try again later.",
plan: "free",
};
}
}
/**
* Clear stored license information
*/
clearLicense(): void {
log.info("[LicenseService] Clearing license information");
// Clear both new and legacy formats
this.storeService.delete("license");
this.storeService.delete("license-key");
this.storeService.delete("license-validation");
}
/**
* Get usage statistics for free users
*/
getUsageStats(): UsageStats {
const stats = this.storeService.get("usageStats") as UsageStats | undefined;
const today = new Date().toISOString().split("T")[0];
if (!stats || stats.lastResetDate !== today) {
// Reset daily stats if it's a new day
const newStats: UsageStats = {
dailyGenerations: 0,
lastResetDate: today,
styleUsage: {},
};
this.storeService.set("usageStats", newStats);
return newStats;
}
return stats;
}
/**
* Record a wallpaper generation for usage tracking
*/
recordWallpaperGeneration(style: string): void {
const license = this.getLicenseInfo();
// Don't track usage for premium users
if (license.isPremium) {
return;
}
const stats = this.getUsageStats();
const updatedStats: UsageStats = {
...stats,
dailyGenerations: stats.dailyGenerations + 1,
styleUsage: {
...stats.styleUsage,
[style]: (stats.styleUsage[style] || 0) + 1,
},
};
this.storeService.set("usageStats", updatedStats);
log.debug(`[LicenseService] Recorded generation for ${style}. Daily: ${updatedStats.dailyGenerations}`);
}
/**
* Check if user can generate a wallpaper for the given style
*/
canGenerateWallpaper(style: string): { canGenerate: boolean; reason?: string } {
const license = this.getLicenseInfo();
const usageStats = this.getUsageStats();
// Premium users can generate unlimited wallpapers
if (license.isPremium) {
return { canGenerate: true };
}
// Free users can only use free styles
if (isStylePremium(style)) {
return {
canGenerate: false,
reason: `${style} requires premium access. Upgrade to unlock all 120+ premium styles!`,
};
}
// Check daily limit for free users
if (usageStats.dailyGenerations >= FREE_WALLPAPERS_PER_DAY) {
return {
canGenerate: false,
reason: `Daily limit reached (${FREE_WALLPAPERS_PER_DAY} wallpapers). Upgrade for unlimited generation!`,
};
}
// Check per-style limit for free users
const styleUsage = usageStats.styleUsage[style] || 0;
if (styleUsage >= FREE_WALLPAPERS_PER_STYLE) {
return {
canGenerate: false,
reason: `You've used all ${FREE_WALLPAPERS_PER_STYLE} free wallpapers for ${style}. Try another free style or upgrade!`,
};
}
return { canGenerate: true };
}
/**
* Check if user can select more styles (for free users with selection limit)
*/
canSelectMoreStyles(currentSelectionCount: number): { canSelect: boolean; reason?: string } {
const license = this.getLicenseInfo();
// Premium users can select unlimited styles
if (license.isPremium) {
return { canSelect: true };
}
// Check selection limit for free users
if (currentSelectionCount >= FREE_STYLE_SELECTION_LIMIT) {
return {
canSelect: false,
reason: `Free users can select up to ${FREE_STYLE_SELECTION_LIMIT} styles. Upgrade for unlimited selection!`,
};
}
return { canSelect: true };
}
/**
* Get remaining style selections for free users
*/
getRemainingStyleSelections(currentSelectionCount: number): number {
const license = this.getLicenseInfo();
if (license.isPremium) {
return -1; // Unlimited
}
return Math.max(0, FREE_STYLE_SELECTION_LIMIT - currentSelectionCount);
}
/**
* Get remaining free generations for today
*/
getRemainingFreeGenerations(): number {
const license = this.getLicenseInfo();
if (license.isPremium) {
return -1; // Unlimited
}
const usageStats = this.getUsageStats();
return Math.max(0, FREE_WALLPAPERS_PER_DAY - usageStats.dailyGenerations);
}
/**
* Get remaining generations for a specific style
*/
getRemainingStyleGenerations(style: string): number {
const license = this.getLicenseInfo();
if (license.isPremium) {
return -1; // Unlimited
}
if (isStylePremium(style)) {
return 0; // Premium style not available to free users
}
const usageStats = this.getUsageStats();
const styleUsage = usageStats.styleUsage[style] || 0;
return Math.max(0, FREE_WALLPAPERS_PER_STYLE - styleUsage);
}
}