71 lines
2.0 KiB
TypeScript
71 lines
2.0 KiB
TypeScript
import { app, safeStorage } from 'electron'
|
|
import { promises as fs } from 'node:fs'
|
|
import path from 'node:path'
|
|
|
|
export interface StoredLicense {
|
|
token: string
|
|
licenseCodeMask: string
|
|
deviceMask: string
|
|
expiresAt?: string
|
|
maxDevices: number
|
|
lastVerifiedAt: string
|
|
}
|
|
|
|
interface StoredLicenseFile {
|
|
encryptedToken: string
|
|
licenseCodeMask: string
|
|
deviceMask: string
|
|
expiresAt?: string
|
|
maxDevices: number
|
|
lastVerifiedAt: string
|
|
}
|
|
|
|
export class LicenseStore {
|
|
private get filePath(): string {
|
|
return path.join(app.getPath('userData'), 'license.json')
|
|
}
|
|
|
|
async load(): Promise<StoredLicense | null> {
|
|
try {
|
|
const raw = await fs.readFile(this.filePath, 'utf8')
|
|
const stored = JSON.parse(raw) as StoredLicenseFile
|
|
const decrypted = await safeStorage.decryptStringAsync(
|
|
Buffer.from(stored.encryptedToken, 'base64'),
|
|
)
|
|
return {
|
|
token: decrypted.result,
|
|
licenseCodeMask: stored.licenseCodeMask,
|
|
deviceMask: stored.deviceMask,
|
|
expiresAt: stored.expiresAt,
|
|
maxDevices: stored.maxDevices,
|
|
lastVerifiedAt: stored.lastVerifiedAt,
|
|
}
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
async save(license: StoredLicense): Promise<void> {
|
|
if (!(await safeStorage.isAsyncEncryptionAvailable())) {
|
|
throw new Error('系统安全凭据存储不可用')
|
|
}
|
|
const encryptedToken = await safeStorage.encryptStringAsync(license.token)
|
|
const stored: StoredLicenseFile = {
|
|
encryptedToken: encryptedToken.toString('base64'),
|
|
licenseCodeMask: license.licenseCodeMask,
|
|
deviceMask: license.deviceMask,
|
|
expiresAt: license.expiresAt,
|
|
maxDevices: license.maxDevices,
|
|
lastVerifiedAt: license.lastVerifiedAt,
|
|
}
|
|
await fs.mkdir(path.dirname(this.filePath), { recursive: true })
|
|
const temporaryPath = `${this.filePath}.tmp`
|
|
await fs.writeFile(temporaryPath, JSON.stringify(stored), { mode: 0o600 })
|
|
await fs.rename(temporaryPath, this.filePath)
|
|
}
|
|
|
|
async clear(): Promise<void> {
|
|
await fs.rm(this.filePath, { force: true })
|
|
}
|
|
}
|