Setup commercial launch: licenze + backend Cloudflare Worker
Client (Python/JS):
- core/license.py: attivazione, validazione, deactivate (HTTP client + JWT decode)
- core/config.py: campi license_* + URL endpoint + costanti grace/revalidate
- api/bridge.py: get_init_data ritorna license status; activate_license/
deactivate_license/revalidate_license/open_purchase_page
- check_update riscritto: punta a musictools.djluza.com/api/latest con
Bearer token, URL di download firmato dal server solo se licensed
- webui: schermata di attivazione bloccante all'avvio; sezione Licenza
in Impostazioni con verifica/disattiva
Backend (server/):
- Cloudflare Worker + D1 + R2 (vedi server/README.md)
- Endpoints: /api/license/{activate,validate,deactivate}, /api/latest,
/api/webhook/lemonsqueezy, /api/health
- JWT HS256 con rotazione; max 3 attivazioni/licenza
- Generazione licenze via webhook Lemon Squeezy + email Resend
- Schema D1 in migrations/0001_init.sql
Memory: nuova musictools-commercial-launch.md, rimosso pending obsoleto
NON taggare finche backend non e' deployato (l'app e' bloccante)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
1 parent
05a2a58ace
commit
5202b2c586
19 files changed
+1572
-31
No files matched your search
+127
@@ -7,6 +7,8 @@ const $$ = (sel) => Array.from(document.querySelectorAll(sel));
|
||||
|
||||
const state = {
|
||||
config: {},
|
||||
license: { licensed: false },
|
||||
purchaseUrl: "https://musictools.djluza.com",
|
||||
loaded: null, // { kind: "urls"|"tracks", urls?, tracks?, count }
|
||||
dlOutputDir: "",
|
||||
upDir: "",
|
||||
@@ -45,6 +47,90 @@ function parseQueryToTrack(q) {
|
||||
return { artist: "", name: s };
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Licenza: gating della UI principale
|
||||
// ============================================================
|
||||
function applyLicenseGate() {
|
||||
const lic = state.license || { licensed: false };
|
||||
const screen = $("#activateScreen");
|
||||
const appEl = document.querySelector(".app");
|
||||
if (!lic.licensed) {
|
||||
if (screen) screen.hidden = false;
|
||||
if (appEl) appEl.style.display = "none";
|
||||
} else {
|
||||
if (screen) screen.hidden = true;
|
||||
if (appEl) appEl.style.display = "";
|
||||
}
|
||||
}
|
||||
|
||||
function fmtDate(epoch) {
|
||||
if (!epoch) return "—";
|
||||
try {
|
||||
return new Date(epoch * 1000).toLocaleDateString("it-IT", {
|
||||
day: "2-digit", month: "short", year: "numeric"
|
||||
});
|
||||
} catch (_e) { return "—"; }
|
||||
}
|
||||
|
||||
function renderLicenseStatus() {
|
||||
const lic = state.license || {};
|
||||
const box = $("#licenseStatusBox");
|
||||
if (!box) return; // panel non ancora montato
|
||||
if (!lic.licensed) {
|
||||
box.innerHTML = `<div class="lic-warn">Licenza non attiva</div>`;
|
||||
return;
|
||||
}
|
||||
box.innerHTML = `
|
||||
<div class="lic-row"><span>Email</span><strong>${lic.email || "—"}</strong></div>
|
||||
<div class="lic-row"><span>Chiave</span><strong>${lic.key || "—"}</strong></div>
|
||||
<div class="lic-row"><span>Attivata il</span><strong>${fmtDate(lic.activated_at)}</strong></div>
|
||||
<div class="lic-row"><span>Ultima verifica</span><strong>${fmtDate(lic.last_validated_at)}</strong></div>
|
||||
`;
|
||||
}
|
||||
|
||||
async function activateLicense() {
|
||||
const email = $("#actEmail").value.trim();
|
||||
const key = $("#actKey").value.trim();
|
||||
const err = $("#actError");
|
||||
err.hidden = true;
|
||||
err.textContent = "";
|
||||
if (!email || !key) {
|
||||
err.textContent = "Inserisci email e chiave di licenza.";
|
||||
err.hidden = false;
|
||||
return;
|
||||
}
|
||||
const btn = $("#actActivateBtn");
|
||||
btn.disabled = true;
|
||||
const oldText = btn.innerHTML;
|
||||
btn.innerHTML = "Attivazione in corso…";
|
||||
try {
|
||||
const res = await window.pywebview.api.activate_license({ email, key });
|
||||
if (res.ok) {
|
||||
state.license = res.license;
|
||||
applyLicenseGate();
|
||||
renderLicenseStatus();
|
||||
toast("Licenza attivata. Benvenuto!", "success");
|
||||
} else {
|
||||
err.textContent = res.error || "Errore di attivazione.";
|
||||
err.hidden = false;
|
||||
}
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = oldText;
|
||||
}
|
||||
}
|
||||
|
||||
async function deactivateLicense() {
|
||||
if (!confirm("Disattivare la licenza su questo dispositivo?")) return;
|
||||
const res = await window.pywebview.api.deactivate_license();
|
||||
if (res.ok) {
|
||||
state.license = res.license;
|
||||
applyLicenseGate();
|
||||
renderLicenseStatus();
|
||||
toast("Licenza disattivata", "info");
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Bridge — eventi spinti dal backend
|
||||
// ============================================================
|
||||
@@ -234,10 +320,15 @@ async function init() {
|
||||
await waitApi();
|
||||
const data = await window.pywebview.api.get_init_data();
|
||||
state.config = data.config || {};
|
||||
state.license = data.license || { licensed: false };
|
||||
state.purchaseUrl = data.purchase_url || "https://musictools.djluza.com";
|
||||
$("#footerVersion").textContent = data.version;
|
||||
$("#currentVersionLabel").textContent = data.version;
|
||||
$("#guideBody").textContent = data.spotify_guide;
|
||||
|
||||
applyLicenseGate();
|
||||
renderLicenseStatus();
|
||||
|
||||
// Populate Settings fields
|
||||
$("#clientIdInput").value = state.config.client_id || "";
|
||||
$("#clientSecretInput").value = state.config.client_secret || "";
|
||||
@@ -855,6 +946,42 @@ $("#checkUpdateBtn").addEventListener("click", async () => {
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// Licenza: listener dello schermo di attivazione + impostazioni
|
||||
// ============================================================
|
||||
$("#actActivateBtn")?.addEventListener("click", activateLicense);
|
||||
$("#actBuyBtn")?.addEventListener("click", async () => {
|
||||
await window.pywebview.api.open_purchase_page();
|
||||
});
|
||||
$("#actKey")?.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Enter") activateLicense();
|
||||
});
|
||||
|
||||
$("#deactivateLicenseBtn")?.addEventListener("click", deactivateLicense);
|
||||
|
||||
$("#revalidateLicenseBtn")?.addEventListener("click", async () => {
|
||||
const btn = $("#revalidateLicenseBtn");
|
||||
btn.disabled = true;
|
||||
const old = btn.textContent;
|
||||
btn.textContent = "Verifica in corso…";
|
||||
try {
|
||||
const res = await window.pywebview.api.revalidate_license();
|
||||
if (res.ok) {
|
||||
state.license = res.license;
|
||||
renderLicenseStatus();
|
||||
if (res.license.licensed) {
|
||||
toast("Licenza verificata", "success");
|
||||
} else {
|
||||
applyLicenseGate();
|
||||
toast("Licenza non piu valida", "error");
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.textContent = old;
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// Boot
|
||||
// ============================================================
|
||||
|
||||
Reference in new issue
Block a user