Skip to content

Commit 596c4cd

Browse files
committed
Test QRCode and upgrade javascript struct
1 parent ce6a736 commit 596c4cd

8 files changed

Lines changed: 448 additions & 684 deletions

File tree

app/static/js/wallet/components.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// --- POPUP REACTION & CLICK-OUTSIDE ---
2+
if (trashIcon && !trashIcon.hasTrashIconHandler) {
3+
trashIcon.addEventListener("click", (e) => {
4+
if (trashPopup.classList.contains("popup-visible")) {
5+
trashPopup.classList.remove("popup-visible");
6+
} else {
7+
const rect = trashIcon.getBoundingClientRect();
8+
trashPopup.style.top = `${rect.bottom + window.scrollY + 5}px`;
9+
trashPopup.style.left = `${rect.left + window.scrollX}px`;
10+
trashPopup.classList.add("popup-visible");
11+
}
12+
});
13+
trashIcon.hasTrashIconHandler = true;
14+
}
15+
16+
if (trashPopupCancel && !trashPopupCancel.hasTrashPopupCancelHandler) {
17+
trashPopupCancel.addEventListener("click", () => trashPopup.classList.remove("popup-visible"));
18+
trashPopupCancel.hasTrashPopupCancelHandler = true;
19+
}
20+
21+
// Chiudi trashPopup se si clicca fuori dall'area
22+
document.addEventListener("click", (e) => {
23+
if (!trashPopup.contains(e.target) && !trashIcon.contains(e.target)) {
24+
trashPopup.classList.remove("popup-visible");
25+
}
26+
});
27+
28+
// --- SELECT DELLO STATO E BANDIERE ---
29+
function updateFlag(selectedValue) {
30+
const selected = selectedValue.toLowerCase();
31+
const flagImg = document.getElementById("flagImage");
32+
if (selected) {
33+
flagImg.src = `/static/images/flags/4x3/${selected}.svg`;
34+
} else {
35+
flagImg.src = `/static/images/flags/4x3/eu.svg`;
36+
}
37+
flagImg.style.display = "block";
38+
}
39+
40+
selectElement.addEventListener('change', function(event) {
41+
updateFlag(event.target.value);
42+
initBtn.disabled = false;
43+
});
44+
45+
// Chiamata di avvio UI all'onload
46+
window.onload = function () {
47+
console.log("Tutto caricato");
48+
handleInitErrorUI();
49+
};
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// --- COMPONENTE AGGIUNGI CREDENZIALE ---
2+
if (addBtn && !addBtn.hasAddHandler) {
3+
addBtn.addEventListener("click", async (e) => {
4+
e.preventDefault();
5+
result.innerHTML = ``;
6+
credPopup.classList.add("show");
7+
credPopupBody.style.minHeight = "300px";
8+
credPopupTitle.innerHTML = "Aggiungi Credenziale";
9+
credPopupBody.innerHTML = `<div class="spinner-wrapper"><div class="spinner"></div><p>Caricamento...</p></div>`;
10+
11+
await new Promise(resolve => setTimeout(resolve, 300));
12+
13+
try {
14+
const response = await executeFetch("/itwallet/credentialSupported");
15+
if (!response.ok) throw new Error(await getErrorMessage(response));
16+
const data = await response.json();
17+
18+
if (data.success) {
19+
credPopupBody.innerHTML = `...[Render Dropdown con data.data]...`; // Costruzione del menu custom dell'originale
20+
// Aggancio logica di selezione ed esecuzione di confirmAddCredential(selectedValue)
21+
} else {
22+
credPopupBody.innerHTML = `<div class="flash-error"><p>${data?.data?.error}</p></div>`;
23+
}
24+
} catch (err) {
25+
credPopupBody.innerHTML = `<div class="flash-error"><p>Fallito recupero credenziali: ${err.message}</p></div>`;
26+
}
27+
});
28+
addBtn.hasAddHandler = true;
29+
}
30+
31+
// --- ISPEZIONE DELLA MEMORIA DEL WALLET ---
32+
if (memoryIcon && !memoryIcon.hasMemoryIconHandler) {
33+
memoryIcon.addEventListener("click", async (e) => {
34+
e.preventDefault();
35+
result.innerHTML = ``;
36+
credPopup.classList.add("show");
37+
credPopupTitle.innerHTML = "Ispeziona memoria";
38+
credPopupBody.innerHTML = `<div class="spinner-wrapper"><div class="spinner"></div><p>Caricamento...</p></div>`;
39+
40+
try {
41+
const response = await executeFetch("/itwallet/objectTypesInMemory");
42+
if (!response.ok) throw new Error(await getErrorMessage(response));
43+
const data = await response.json();
44+
45+
if (data.success) {
46+
const mappedTypes = data.data.map(type => ({ id: type, label: type, icon: "📦" }));
47+
credPopupBody.innerHTML = `...[Render Dropdown per Oggetti in memoria]...`;
48+
// Aggancio logica per confirmViewObjectTypeInMemory(selectedValue)
49+
} else {
50+
credPopupBody.innerHTML = `<div class="flash-error"><p>${data?.data?.error}</p></div>`;
51+
}
52+
} catch (err) {
53+
credPopupBody.innerHTML = `<div class="flash-error"><p>Errore memoria: ${err.message}</p></div>`;
54+
}
55+
});
56+
memoryIcon.hasMemoryIconHandler = true;
57+
}
58+
59+
// --- DETTAGLIO MODALE BOOTSTRAP (Visualizzazione dati credenziale) ---
60+
if (template) {
61+
template.addEventListener('shown.bs.modal', async function (event) {
62+
const button = event.relatedTarget;
63+
const button_split = button.id.split(":");
64+
const input_body = {
65+
issuer: button_split.slice(0, -1).join(":"),
66+
key: button_split.pop()
67+
};
68+
try {
69+
const response = await executeFetch("/wallet/detail", "POST", input_body);
70+
if (!response.ok) throw new Error(await getErrorMessage(response));
71+
document.getElementById("modal-body-detail").innerHTML = await response.text();
72+
} catch (err) {
73+
console.error(err);
74+
}
75+
});
76+
}

app/static/js/wallet/init-ui.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Inizializzazione Carosello esterno
2+
document.addEventListener('DOMContentLoaded', function () {
3+
new Splide('.it-carousel-wrapper', {
4+
type: 'loop',
5+
perPage: 3,
6+
gap: '1rem',
7+
height: 'auto',
8+
arrows: true,
9+
}).mount();
10+
});
11+
12+
// Funzione di blocco UI in caso di errore di inizializzazione a monte
13+
function handleInitErrorUI() {
14+
if (!INIT_ERROR_MESSAGE) return;
15+
console.log("Init error rilevato:", INIT_ERROR_MESSAGE);
16+
17+
if (idpDiv) {
18+
const radios = idpDiv.querySelectorAll("input[type='radio']");
19+
radios.forEach(radio => radio.disabled = true);
20+
idpDiv.style.opacity = "0.5";
21+
idpDiv.style.pointerEvents = "none";
22+
document.getElementById("state_container").style.display = "none";
23+
}
24+
if (selectElement) {
25+
selectElement.disabled = true;
26+
}
27+
const buttonsContainer = document.getElementById("buttons_containers");
28+
if (buttonsContainer) {
29+
const buttons = buttonsContainer.querySelectorAll("button, a");
30+
buttons.forEach(btn => {
31+
btn.disabled = true;
32+
btn.classList.add("disabled");
33+
});
34+
buttonsContainer.style.opacity = "0.5";
35+
buttonsContainer.style.pointerEvents = "none";
36+
buttonsContainer.style.display = "none";
37+
}
38+
}
39+
40+
// Comportamento dello spinner in caso di click
41+
if (!spinner.hasPopupFocusHandler) {
42+
spinner.addEventListener("click", () => {
43+
if (popupHtml && !popupHtml.closed) popupHtml.focus();
44+
});
45+
spinner.hasPopupFocusHandler = true;
46+
}
47+
48+
// Chiusura popups con il tasto ESC
49+
document.addEventListener("keydown", (e) => {
50+
if (e.key === "Escape") {
51+
closeCredPopup(); // Nota: assicurati che questa funzione sia definita globalmente
52+
}
53+
});

app/static/js/wallet/lifecycle.js

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// --- FLUSSO INIZIALIZZAZIONE WALLET ---
2+
// (Incluso dentro window.onload nell'originale)
3+
if (initBtn && !initBtn.hasInitHandler) {
4+
initBtn.addEventListener("click", async (e) => {
5+
e.preventDefault();
6+
const selectedIdpRadio = document.querySelector('input[name="idpRadio"]:checked');
7+
if (!selectedIdpRadio) {
8+
result.innerHTML = `<div class="alert alert-warning...>Scegli come verificare la tua identità.</div>`;
9+
return;
10+
}
11+
const selectedState = selectElement.value;
12+
if (!selectedState) {
13+
result.innerHTML = `<div class="alert alert-warning...">Seleziona uno stato membro...</div>`;
14+
return;
15+
}
16+
17+
result.innerHTML = ``;
18+
spinner.style.display = "flex";
19+
const url = `/itwallet/init?idp=${selectedIdpRadio.value}&country=${encodeURIComponent(selectedState)}`;
20+
21+
try {
22+
const response = await executeFetch(url);
23+
if (!response.ok) throw new Error(await getErrorMessage(response));
24+
const data = await response.json();
25+
26+
if (data.success) {
27+
result.innerHTML = "";
28+
const redirectUrl = data?.data?.redirect_url;
29+
const popupWidth = 500;
30+
const popupHeight = 660;
31+
const left = window.screenX + (window.outerWidth - popupWidth) / 2;
32+
const top = window.screenY + (window.outerHeight - popupHeight) / 2;
33+
34+
popupHtml = window.open(redirectUrl, "walletPopup", `width=${popupWidth},height=${popupHeight},left=${left},top=${top},resizable=yes,scrollbars=yes`);
35+
if (!popupHtml) { alert("Popup bloccato!"); return; }
36+
popupHtml.focus();
37+
38+
const popupInterval = setInterval(() => {
39+
try {
40+
if (popupHtml.closed) { clearInterval(popupInterval); window.focus(); spinner.style.display = "none"; }
41+
} catch { /* cross-origin */ }
42+
}, 500);
43+
} else {
44+
result.innerHTML = `<div class="alert alert-danger...">...</div>`;
45+
}
46+
} catch (err) {
47+
result.innerHTML = `<div class="alert alert-danger...">Errore: ${err.message}</div>`;
48+
} finally {
49+
if (!popupHtml) spinner.style.display = "none";
50+
}
51+
});
52+
initBtn.hasInitHandler = true;
53+
}
54+
55+
// --- COMPLETAMENTO DA POPUP EXTERNO (OIDC/OAuth PostMessage) ---
56+
window.addEventListener("message", async (event) => {
57+
if (event.data?.event === "wallet_flow_complete") {
58+
spinner.style.display = "flex";
59+
spinner.classList.add("force-visible");
60+
result.innerHTML = `<div class="flash-container flash-success"><p>Autenticazione completata, recupero PID...</p></div>`;
61+
62+
try {
63+
const response = await executeFetch("/itwallet/init/complete", "GET");
64+
if (!response.ok) throw new Error(await getErrorMessage(response));
65+
const data = await response.json();
66+
67+
if (data.success) {
68+
result.innerHTML = `<div class="flash-container flash-success"><p>Wallet inizializzato con successo!</p></div>`;
69+
addButton(data?.data?.credential_id); // Nota: deve essere definita altrove
70+
71+
// Toggle visibilità elementi UI post-inizializzazione
72+
initBtn.style.display = "none";
73+
addBtn.style.display = "inline-flex";
74+
rpBtn.style.display = "inline-flex";
75+
statesDiv.style.display = "none";
76+
idpDiv.style.display = "none";
77+
credentialsBtnDiv.style.display = "flex";
78+
trashIcon.style.display = "block";
79+
memoryIcon.style.display = "block";
80+
} else {
81+
result.innerHTML = `<div class="flash-container flash-error"><p>${data?.data?.error}</p></div>`;
82+
}
83+
} catch (error) {
84+
result.innerHTML = `<div class="flash-container flash-error"><p>${error.message}</p></div>`;
85+
} finally {
86+
spinner.classList.remove("force-visible");
87+
spinner.style.display = "none";
88+
}
89+
}
90+
});
91+
92+
// --- RESET DEL WALLET (TRASH CONFIRM) ---
93+
if (trashPopupConfirm && !trashPopupConfirm.hasTrashPopupConfirmHandler) {
94+
trashPopupConfirm.addEventListener("click", async () => {
95+
trashPopup.classList.remove("popup-visible");
96+
spinner.style.display = "flex";
97+
await new Promise(resolve => setTimeout(resolve, 300));
98+
99+
try {
100+
const response = await executeFetch("/itwallet/reset");
101+
if (!response.ok) throw new Error(await getErrorMessage(response));
102+
103+
result.innerHTML = `<div class="alert alert-success...">Il Wallet è stato resettato correttamente</div>`;
104+
105+
// Reset grafico dell'interfaccia allo stato iniziale
106+
if (addBtn) addBtn.style.display = "none";
107+
if (rpBtn) rpBtn.style.display = "none";
108+
if (credentialsBtnDiv) { credentialsBtnDiv.style.display = "none"; credentialsBtnDiv.innerHTML = ""; }
109+
if (statesDiv) { statesDiv.style.display = "block"; selectElement.value = ""; }
110+
if (initBtn) { initBtn.style.display = "inline-flex"; initBtn.disabled = true; }
111+
if (idpDiv) idpDiv.style.display = "block";
112+
} catch (err) {
113+
result.innerHTML = `<div class="alert alert-danger..."><strong>Error:</strong> ${err.message}</div>`;
114+
} finally {
115+
spinner.style.display = "none";
116+
}
117+
});
118+
trashPopupConfirm.hasTrashPopupConfirmHandler = true;
119+
}

app/static/js/wallet/rp-scanner.js

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// --- COMPONENTE ENTRA CON WALLET (RP) ---
2+
if (rpBtn && !rpBtn.hasAddHandler) {
3+
rpBtn.addEventListener("click", async (e) => {
4+
e.preventDefault();
5+
result.innerHTML = ``;
6+
credPopup.classList.add("show");
7+
credPopupTitle.innerHTML = "Relying Party";
8+
// Caricamento asincrono di /itwallet/onboardedRelyingParties e creazione della UI di scelta RP
9+
});
10+
rpBtn.hasAddHandler = true;
11+
}
12+
13+
// --- SISTEMA DI SCANSIONE QR (CAMERA) ---
14+
let qrStream = null;
15+
let qrAnimationId = null;
16+
let isQrScanning = false;
17+
18+
function apriQrPopup() {
19+
const qrModalElement = document.getElementById('qr-scanner-modal');
20+
const qrModal = new bootstrap.Modal(qrModalElement);
21+
qrModal.show();
22+
startQrScanner();
23+
}
24+
25+
async function startQrScanner() {
26+
const video = document.getElementById("qr-video");
27+
const canvasElement = document.getElementById("qr-canvas");
28+
const canvas = canvasElement.getContext("2d", { willReadFrequently: true });
29+
const textArea = document.getElementById("extra-info");
30+
const confirmBtn = document.getElementById("confirmLoginRP");
31+
32+
try {
33+
qrStream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" } });
34+
video.srcObject = qrStream;
35+
video.setAttribute("playsinline", true);
36+
video.play();
37+
isQrScanning = true;
38+
qrAnimationId = requestAnimationFrame(tick);
39+
} catch (err) {
40+
console.error("Impossibile accedere alla fotocamera:", err);
41+
alert("Errore di accesso alla fotocamera. Verifica i permessi.");
42+
stopQrScanner();
43+
}
44+
45+
function tick() {
46+
if (!isQrScanning) return;
47+
if (video.readyState === video.HAVE_ENOUGH_DATA) {
48+
canvasElement.height = video.videoHeight;
49+
canvasElement.width = video.videoWidth;
50+
canvas.drawImage(video, 0, 0, canvasElement.width, canvasElement.height);
51+
const imageData = canvas.getImageData(0, 0, canvasElement.width, canvasElement.height);
52+
53+
// jsQR è una dipendenza globale esterna
54+
const code = jsQR(imageData.data, imageData.width, imageData.height, { inversionAttempts: "dontInvert" });
55+
56+
if (code) {
57+
textArea.value = code.data;
58+
if (confirmBtn) confirmBtn.disabled = false;
59+
const qrModal = bootstrap.Modal.getInstance(document.getElementById('qr-scanner-modal'));
60+
if (qrModal) qrModal.hide();
61+
stopQrScanner();
62+
return;
63+
}
64+
}
65+
qrAnimationId = requestAnimationFrame(tick);
66+
}
67+
}
68+
69+
function stopQrScanner() {
70+
isQrScanning = false;
71+
if (qrAnimationId) { cancelAnimationFrame(qrAnimationId); qrAnimationId = null; }
72+
if (qrStream) { qrStream.getTracks().forEach(track => track.stop()); qrStream = null; }
73+
console.log("Scanner QR interrotto.");
74+
}

0 commit comments

Comments
 (0)