import type { LicenseInfo, LicenseCheckResult, SavedLicense } from '@/types/license'; import i18n from '@/i18n'; import { LicenseStorage } from '@/utils/license-storage'; import { PermissionService } from '@/utils/permissions'; import { Notification } from 'kintone-ui-component/lib/notification'; import { MobileNotification } from 'kintone-ui-component/lib/mobile/notification'; import manifestJson from '@/manifest.json'; import { KJUR } from 'jsrsasign'; import rsaPublicKey from '../../rsa_public.pem?raw'; import config from '@/config.json'; const { t: $t } = i18n.global; export class LicenseService { // 常量定义 private static PLUGIN_ID: string = ''; // ============ 基础工具函数 ============ /** * 获取域名 */ static getDomain(): string { return window.location.hostname; } /** * 获取插件名 */ static getPluginName(): string { const pluginName = manifestJson.name as Record; const userLang = kintone.getLoginUser().language; const plugin = pluginName[userLang] || pluginName.ja; return plugin; } /** * 获取插件ID */ static getPluginId(): string { return this.PLUGIN_ID || new URLSearchParams(window.location.search).get('pluginId') || ''; } /** * 检查许可证是否有效 * 参数是解码后的JWT结构,验证签名和payload进行原有检查 */ static checkLicenseAvailable(savedLicense: SavedLicense): boolean { try { // 验证完整的JWT const result = KJUR.jws.JWS.verifyJWT(savedLicense.jwt, rsaPublicKey.trim(), { alg: ['RS256'] }); if (!result) { console.warn($t('license.error.jwtFailed', { e: '' })); return false; } } catch (error) { console.warn($t('license.error.jwtFailed', { e: error })); return false; } const license = savedLicense.licenseInfo; const domain = this.getDomain(); const pluginId = this.getPluginId(); // 检查域名和插件ID是否与当前环境一致 if (license.domain !== domain || license.pluginId !== pluginId) { return false; } // 检查是否付费 if (license.isPaid) { return true; } // 检查存储是否过期(是否同一天) if (!this.isToday(license.fetchTime)) { // 不是同一天,已过期 return false; } // 检查试用是否到期 const expiredTime = new Date(license.expiredTime); if (expiredTime < new Date()) { return false; } return true; } static isToday(timestamp: number): boolean { const fetchDate = new Date(timestamp).toDateString(); const todayStr = new Date().toDateString(); return fetchDate == todayStr; } /** * 许可证验证 */ static async checkLicense(): Promise { if (!config.license.enabled || rsaPublicKey.trim() === '') { return { isLicenseValid: true, license: undefined, }; } const localLicense = this.getLocalLicenseInfo() || undefined; if (localLicense) { return { isLicenseValid: true, license: localLicense, }; } return await this.checkLicenseRemote(); } /** * 调用远程许可证API */ private static async callRemoteLicenseAPI(domain: string, pluginId: string): Promise { const url = config.license.api.checkUrl; const method = 'POST'; const headers = { 'Content-Type': 'application/json' }; const body = { domain, pluginId, pluginKey: config.license.api.pluginKey, }; const proxyResponse = await kintone.proxy(url, method, headers, body); if (proxyResponse[1] !== 200) { throw new Error(`API request failed with status: ${proxyResponse[1]}`); } const response = JSON.parse(proxyResponse[0]); if (!response || !response.success || !response.jwt) { return null; } return response.jwt; } /** * 远程许可证验证 */ static async checkLicenseRemote(): Promise { try { const domain = this.getDomain(); const pluginId = this.getPluginId(); const jwt = await this.callRemoteLicenseAPI(domain, pluginId); if (!jwt) { return { isLicenseValid: false, isRemote: true, }; } // 保存 JWT 到本地存储,获取保存的许可证结构 const savedLicense = LicenseStorage.saveLicense(jwt); if (!savedLicense) { return { isLicenseValid: false, isRemote: true, }; } // 使用解析后的JWT进行验证 const isValid = this.checkLicenseAvailable(savedLicense); return { isLicenseValid: isValid, license: savedLicense.licenseInfo, isRemote: true, }; } catch (error) { console.error($t('license.error.fetchFailed', { e: error })); return { isLicenseValid: false, isRemote: true, }; } } // ============ 数据处理函数 ============ /** * 检查是否快要到期 */ static isExpiringSoon(expiryTimestamp: number): boolean { const now = Date.now(); const warningTime = expiryTimestamp - config.license.warningDaysBeforeExpiry * 24 * 60 * 60 * 1000; return now >= warningTime && expiryTimestamp > now; } /** * 格式化到期时间 */ static formatExpiryDate(timestamp: number): string { if (timestamp === -1) return $t('license.status.permanent'); return new Date(timestamp).toLocaleDateString(kintone.getLoginUser().language); } /** * 计算剩余天数 */ static getDaysRemaining(timestamp: number): number { if (timestamp === -1) return Infinity; const now = new Date().getTime(); const remainingMs = timestamp - now; return Math.floor(remainingMs / (24 * 60 * 60 * 1000)); } // ============ UI显示函数 ============ /** * 显示到期警告通知 */ static async showNotification(license: LicenseInfo | undefined, isWarning: boolean) { let message; const plugin = this.getPluginName(); if (isWarning) { // 尚未到期 const remainingDays = this.getDaysRemaining(license!.expiredTime); if (remainingDays < 0) { return; } const days = $t('license.notification.days', remainingDays); message = $t('license.notification.warning', { plugin, days }); } else { // 既に期限切れ message = $t('license.notification.expired', { plugin }); } if (await kintone.isMobilePage()) { const notification = new MobileNotification({ content: message, }); notification.open(); } else { const link = `https://alicorn.cybozu.com/k/admin/app/${kintone.app.getId()}/plugin/config?pluginId=${this.getPluginId()}`; const notification = new Notification({ content: message + '
' + $t('license.notification.gotoLink', { link }), type: isWarning ? 'info' : 'danger', }); notification.open(); } } // ============ 主要入口函数 ============ /** * 检查插件功能访问权限并加载插件(如果获得授权) */ static async loadPluginIfAuthorized(pluginId: string, callback: () => void | Promise) { this.PLUGIN_ID = pluginId; try { if (!config.license.enabled) { // 许可证功能未启用,直接加载插件 await callback(); return; } // 检查许可证(内部已经包含本地检查无效时自动获取远程的逻辑) const licenseCheck = await this.checkLicense(); // 检查权限 const permissions = await PermissionService.checkPermissions(); const isManager = permissions.canManagePlugins; // 许可证无效的情况,直接返回不加载 if (!licenseCheck.isLicenseValid) { if (!isManager) { // 普通用户静默输出 console.warn($t('license.status.expiredDisplay')); } else { // 管理员可以看到过期弹框 this.showNotification(licenseCheck.license, false); } return; } // 许可证有效,如果快要到期,管理员可以看到警告 if ( isManager && licenseCheck.license && !licenseCheck.license.isPaid && this.isExpiringSoon(licenseCheck.license.expiredTime) ) { // 管理员可以看到过期弹框 this.showNotification(licenseCheck.license, true); } // 许可证有效,可以加载插件功能 await callback(); } catch (error) { console.warn($t('license.error.checkFailed')); } } // ============ 许可证信息管理 ============ /** * 获取许可证信息(用于显示) */ static getLocalLicenseInfo(): LicenseInfo | null { const savedLicense = LicenseStorage.getLicense(this.getPluginId()); if (!savedLicense) return null; return savedLicense.licenseInfo; } /** * 强制刷新许可证(清除本地缓存重新检查) */ static async forceRefreshLicense(): Promise { // 清除本地缓存 LicenseStorage.clearLicense(this.getPluginId()); return await this.checkLicenseRemote(); } }