import { app } from 'electron' import { createHash } from 'node:crypto' import os from 'node:os' import { machineIdSync } from 'node-machine-id' import type { AppInfo, LicenseSession, LicenseViewState, VersionInfo, } from '../../src/shared/types' import { LicenseStore, type StoredLicense } from './license-store' import { DesktopServerApi, type DevicePayload, ServerApiError, } from './server-api' const OFFLINE_GRACE_MS = 72 * 60 * 60 * 1000 export class LicenseManager { private readonly store = new LicenseStore() private readonly server = new DesktopServerApi() getAppInfo(): AppInfo { return { name: '正版情趣王', version: app.getVersion(), platform: process.platform, deviceName: os.hostname(), } } async getState(): Promise { const stored = await this.store.load() if (!stored) { return { status: 'unlicensed', message: '输入激活码后即可开始使用', offline: false, } } return this.validateStored(stored) } async activate(activationCode: string): Promise { try { const session = await this.server.activate(activationCode, this.devicePayload()) if (!session.token) { throw new ServerApiError('INVALID_RESPONSE', '授权服务未返回设备凭据') } await this.persistSession(session) return { status: 'active', message: '设备已激活', offline: false, session, version: session.version, } } catch (error) { return this.errorState(error) } } async validate(): Promise { const stored = await this.store.load() if (!stored) { return { status: 'unlicensed', message: '当前设备尚未激活', offline: false, } } return this.validateStored(stored) } async clear(): Promise { await this.store.clear() return { status: 'unlicensed', message: '本机授权凭据已清除', offline: false, } } checkVersion(): Promise { return this.server.checkVersion(app.getVersion()) } private async validateStored(stored: StoredLicense): Promise { try { const session = await this.server.login(stored.token, this.devicePayload()) session.token = stored.token await this.persistSession(session) return { status: 'active', message: '授权有效', offline: false, session, version: session.version, } } catch (error) { if (error instanceof ServerApiError && error.code.startsWith('NETWORK_') && this.canUseOffline(stored)) { const session: LicenseSession = { licenseCodeMask: stored.licenseCodeMask, deviceMask: stored.deviceMask, expiresAt: stored.expiresAt, maxDevices: stored.maxDevices, } return { status: 'active', message: '当前处于离线授权模式,请在 72 小时内联网校验', offline: true, session, } } return this.errorState(error) } } private async persistSession(session: LicenseSession): Promise { if (!session.token) { throw new Error('缺少设备授权凭据') } await this.store.save({ token: session.token, licenseCodeMask: session.licenseCodeMask, deviceMask: session.deviceMask, expiresAt: session.expiresAt, maxDevices: session.maxDevices, lastVerifiedAt: new Date().toISOString(), }) } private canUseOffline(stored: StoredLicense): boolean { const lastVerifiedAt = new Date(stored.lastVerifiedAt).getTime() const expiresAt = stored.expiresAt ? new Date(stored.expiresAt).getTime() : Number.POSITIVE_INFINITY return Number.isFinite(lastVerifiedAt) && Date.now() - lastVerifiedAt <= OFFLINE_GRACE_MS && Date.now() < expiresAt } private errorState(error: unknown): LicenseViewState { if (error instanceof ServerApiError) { return { status: error.code.startsWith('NETWORK_') ? 'network-error' : 'invalid', message: error.message, offline: false, } } return { status: 'invalid', message: error instanceof Error ? error.message : '授权校验失败', offline: false, } } private devicePayload(): DevicePayload { const deviceId = createHash('sha256') .update(`正版情趣王:${machineIdSync()}`) .digest('hex') return { deviceId, deviceName: os.hostname(), osName: `${os.type()} ${os.arch()}`, osVersion: os.release(), appVersion: app.getVersion(), } } }