"use strict"; const crypto = require("node:crypto"); const fs = require("node:fs"); const path = require("node:path"); const { atomicWriteJson } = require("../storage/atomicFile"); const { loadGuidanceDocuments } = require("./guidanceDocuments"); const LICENSE_NAME = "CH-J Proprietary Software License"; const LICENSE_VERSION = "1.14"; class LicenseAcceptance { constructor({ storageRoot, licensePath, appVersion, buildId, docsRoot = path.resolve(__dirname, "../../../docs") }) { this.filePath = path.join(storageRoot, "legal", "license-acceptance.json"); this.text = fs.readFileSync(licensePath, "utf8"); if (!/^CH-J\s+Proprietary Software License\s*\n\s*Version 1\.14\b/.test(this.text) || this.text.length < 10000 || !this.text.trimEnd().endsWith("End of License.")) { throw new Error("The complete CH-J application license 1.14 is required."); } const guidance = loadGuidanceDocuments(docsRoot); this.guidance = guidance.documents; this.identity = Object.freeze({ license: LICENSE_NAME, licenseVersion: LICENSE_VERSION, sha256: crypto.createHash("sha256").update(this.text).digest("hex"), appVersion, buildId, guidanceSha256: guidance.sha256 }); } isAccepted() { try { const record = JSON.parse(fs.readFileSync(this.filePath, "utf8")); return record.schemaVersion === 1 && record.accepted === true && record.guidanceDisplayed === true && typeof record.acceptedAt === "string" && Number.isFinite(Date.parse(record.acceptedAt)) && Object.entries(this.identity).every(([key, value]) => record[key] === value); } catch { return false; } } accept(payload) { if (payload?.accepted !== true || payload?.sha256 !== this.identity.sha256 || payload?.guidanceShown !== true || payload?.guidanceSha256 !== this.identity.guidanceSha256) { throw new Error("Explicit acceptance of the displayed license is required."); } atomicWriteJson(this.filePath, { schemaVersion: 1, ...this.identity, accepted: true, guidanceDisplayed: true, acceptedAt: new Date().toISOString() }); } } module.exports = { LicenseAcceptance, LICENSE_NAME, LICENSE_VERSION };