const serialResultsDiv = document.getElementById("serialResults"); const readBar = document.getElementById("readBar"); // Buffer for accumulating received data before logging let receivedDataBuffer = ''; // Software tool specific variables let deviceSerialNumber = ""; let deviceVersion = ""; let deviceHwVersion = ""; let selectedSoftwareUrl = ""; // Serial port variables (port, writer, textEncoder, writableStreamClosed declared in PHP) let reader; let readableStreamClosed; let keepReading = true; // Function to log communication to API (reused from scripts.js) async function logCommunication(data, direction) { // Only log if debug mode is enabled if (typeof DEBUG === 'undefined' || !DEBUG) { return; } try { const serviceToken = document.getElementById("servicetoken")?.innerHTML || ''; let serialNumber = ''; if (deviceSerialNumber) { serialNumber = deviceSerialNumber; } const logData = { data: data, direction: direction, timestamp: new Date().toISOString(), serial_number: serialNumber, maintenance_run: 0 }; const url = link + '/v2/com_log/log'; const bearer = 'Bearer ' + serviceToken; const response = await fetch(url, { method: 'POST', withCredentials: true, credentials: 'include', headers: { 'Authorization': bearer, 'Content-Type': 'application/json' }, body: JSON.stringify(logData) }); if (!response.ok) { console.warn('Failed to log communication:', response.status); } } catch (error) { console.warn('Error logging communication:', error); } } // Progress bar function (reused from scripts.js) function progressBar(percentage, message, color){ readBar.style.background = color; readBar.style.width = percentage +"%"; readBar.innerHTML = message; } // Connect device for software tool async function connectDeviceForSoftware() { //clear input readBar.innerHTML = ''; serialResultsDiv.innerHTML = ''; document.getElementById("softwareCheckStatus").style.display = "none"; document.getElementById("softwareOptions").style.display = "none"; document.getElementById("noUpdatesMessage").style.display = "none"; document.getElementById("uploadSection").style.display = "none"; // Reset data receivedDataBuffer = ''; deviceSerialNumber = ""; deviceVersion = ""; deviceHwVersion = ""; //set progress bar progressBar("1", "", ""); // Check if DEBUG mode is enabled - use mock device data if (typeof DEBUG !== 'undefined' && DEBUG) { // TEST MODE: Use mock device data deviceSerialNumber = "22110095"; deviceVersion = "03e615af"; deviceHwVersion = "0000080"; document.getElementById("Device_output").style.display = "block"; serialResultsDiv.innerHTML = `DEBUG MODE - Simulated Device Data:
SN=${deviceSerialNumber}
FW=${deviceVersion}
HW=${deviceHwVersion}`; progressBar("60", "DEBUG MODE - Device data read - SN: " + deviceSerialNumber, "#ff9800"); await logCommunication(`DEBUG MODE: Simulated device - SN=${deviceSerialNumber}, Version=${deviceVersion}, HW=${deviceHwVersion}`, 'handshake'); // Proceed to check software availability checkSoftwareAvailability(); return; } // Check if port is already open - if so, refresh the page to start clean if (port) { await logCommunication('Port already in use - refreshing page for clean reconnect', 'info'); location.reload(); return; } // Reset flags for new connection keepReading = true; try { // Prompt user to select any serial port. const filters = [{ usbVendorId: 1027, usbProductId: 24597 }]; port = await navigator.serial.requestPort({ filters }); // Log selected port details const portInfo = port.getInfo(); const portDetails = { processStep: 'Software', usbVendorId: portInfo.usbVendorId, usbProductId: portInfo.usbProductId, readable: !!port.readable, writable: !!port.writable, opened: port.readable !== null && port.writable !== null }; await logCommunication(`Selected USB device - ${JSON.stringify(portDetails)}`, 'connected'); await port.open({ baudRate: 56700, dataBits: 8, stopBits: 1, parity: 'none', flowControl: 'none' }); progressBar("10", "Connecting", "#04AA6D"); // Log successful connection await logCommunication('Port opened successfully', 'connected'); textEncoder = new TextEncoderStream(); writableStreamClosed = textEncoder.readable.pipeTo(port.writable); writer = textEncoder.writable.getWriter(); progressBar("20", "Connected, reading device...", "#04AA6D"); // Read device output await readDeviceOutput(); // Close the port immediately after reading await closePortAfterRead(); // Check for software updates (port is now closed) checkSoftwareAvailability(); } catch (error) { await logCommunication(`Connection error: ${error.message}`, 'error'); // Check for specific "No port selected" error and show user-friendly message if (error.message && error.message.includes('No port selected by the user')) { progressBar("100", "No device selected, please try again", "#ff6666"); } else { progressBar("100", "Error: " + error.message, "#ff6666"); } } } async function readDeviceOutput() { const textDecoder = new TextDecoderStream(); readableStreamClosed = port.readable.pipeTo(textDecoder.writable); reader = textDecoder.readable.getReader(); document.getElementById("Device_output").style.display = "block"; let dataCompleteMarkerFound = false; const startTime = Date.now(); const timeout = 10000; // 10 second timeout try { progressBar("40", "Reading device...", "#04AA6D"); while (keepReading) { const { value, done } = await reader.read(); if (done || !keepReading) { break; } receivedDataBuffer += value; serialResultsDiv.innerHTML = receivedDataBuffer; // Check if we have received PLUGTY which comes after all device data // Or wait for at least FWDATE which comes near the end if (receivedDataBuffer.indexOf("PLUGTY") > 0 || receivedDataBuffer.indexOf("FWDATE=") > 0) { dataCompleteMarkerFound = true; } // Only parse and exit if we found the completion marker if (dataCompleteMarkerFound) { // Parse device info from complete output parseDeviceInfo(receivedDataBuffer); // Check if we have all required data if (deviceSerialNumber && deviceVersion && deviceHwVersion) { progressBar("60", "Device data read - SN: " + deviceSerialNumber, "#04AA6D"); await logCommunication(`Device identification data received: SN=${deviceSerialNumber}, Version=${deviceVersion}, HW=${deviceHwVersion}`, 'handshake'); // Exit cleanly break; } } // Timeout check if (Date.now() - startTime > timeout) { await logCommunication('Device read timeout - proceeding with partial data', 'error'); parseDeviceInfo(receivedDataBuffer); break; } } progressBar("65", "Device reading completed", "#04AA6D"); } catch (error) { if (keepReading) { await logCommunication(`Read error: ${error.message}`, 'error'); progressBar("0", "Error reading device: " + error.message, "#ff6666"); } } finally { // Clean up reader and cancel the stream try { if (reader) { await reader.cancel(); reader.releaseLock(); } } catch (e) { console.log('Reader cleanup error:', e); } } } function parseDeviceInfo(data) { // Extract SN, FW (firmware/software version), and HW from device output // Device format: SN=12345678;FW=12345678;HW=12345678;STATE=... // Match exact logic from scripts.js and readdevice.js // Use exact same approach as scripts.js line 153 and readdevice.js line 649 const x = Array.from(new Set(data.split(";"))).toString(); // Parse SN= (8 characters, exact logic from scripts.js line 185-189) if (x.indexOf("SN=") > 0 && !deviceSerialNumber) { const a = x.indexOf("SN="); const b = a + 3; const c = b + 8; deviceSerialNumber = x.substring(b, c); console.log("Found SN:", deviceSerialNumber); } // Parse FW= (8 characters, exact logic from readdevice.js line 672-676) if (x.indexOf("FW=") > 0 && !deviceVersion) { const a = x.indexOf("FW="); const b = a + 3; const c = b + 8; deviceVersion = x.substring(b, c); console.log("Found FW/Version:", deviceVersion); } // Parse HW= (8 characters, exact logic from readdevice.js line 665-670) if (x.indexOf("HW=") > 0 && !deviceHwVersion) { const a = x.indexOf("HW="); const b = a + 3; const c = b + 8; deviceHwVersion = x.substring(b, c); console.log("Found HW Version:", deviceHwVersion); } } async function closePortAfterRead() { if (port) { try { await logCommunication('Closing port after reading device data', 'info'); // Reader is already cancelled and released by readDeviceOutput finally block // Now abort the writer if (writer) { try { await writer.abort(); } catch (e) { console.log('Writer abort error:', e); } } // Give time for streams to cleanup await new Promise(resolve => setTimeout(resolve, 300)); // Close the port await port.close(); await logCommunication('Port closed successfully', 'info'); // Reset for next connection reader = null; writer = null; readableStreamClosed = null; writableStreamClosed = null; port = null; } catch (error) { console.error('Error closing port after read:', error); await logCommunication(`Error closing port: ${error.message}`, 'error'); // Force reset even on error reader = null; writer = null; readableStreamClosed = null; writableStreamClosed = null; port = null; } } } async function closePort() { if (port) { try { keepReading = false; // Wait for read operations to complete await new Promise(resolve => setTimeout(resolve, 300)); // Release reader if still locked if (reader) { try { reader.releaseLock(); } catch (e) { console.log('Reader release error:', e); } } // Wait a bit more await new Promise(resolve => setTimeout(resolve, 200)); // Now close the port try { await port.close(); await logCommunication('Port closed', 'info'); } catch (e) { console.log('Port close error (may already be closed):', e); } // Reset for next connection reader = null; port = null; } catch (error) { console.error('Error closing port:', error); } } } async function checkSoftwareAvailability() { if (!deviceSerialNumber) { progressBar("0", "Error: Serial number not found", "#ff6666"); await logCommunication('Serial number not found in device output', 'error'); return; } document.getElementById("softwareCheckStatus").style.display = "block"; progressBar("70", "Checking for software updates...", "#04AA6D"); try { // Call software_available API const availableUrl = link + "/v2/software_available/sn=" + deviceSerialNumber + (deviceVersion ? "&version=" + deviceVersion : "") + (deviceHwVersion ? "&hw_version=" + deviceHwVersion : ""); console.log("Calling API:", availableUrl); await logCommunication(`Checking software availability for SN: ${deviceSerialNumber}`, 'sent'); const availableResponse = await fetch(availableUrl, { method: "GET", headers: { "Authorization": "Bearer " + document.getElementById("servicetoken").textContent, "Content-Type": "application/json" } }); const availableData = await availableResponse.json(); console.log("Available response:", availableData); await logCommunication(`Software availability response: ${JSON.stringify(availableData)}`, 'received'); if (availableData.software_available === "error" || availableData.error) { // Error checking for updates (e.g., invalid hardware version) document.getElementById("softwareCheckStatus").style.display = "none"; progressBar("0", "Error: " + (availableData.error || "Unable to check for updates"), "#ff6666"); alert("Error checking for updates: " + (availableData.error || "Unknown error")); } else if (availableData.software_available === "yes") { // Software updates available, fetch options progressBar("80", "Software updates found, loading options...", "#04AA6D"); await fetchSoftwareOptions(); } else { // No updates available document.getElementById("softwareCheckStatus").style.display = "none"; document.getElementById("noUpdatesMessage").style.display = "block"; progressBar("100", "No software updates available", "#04AA6D"); } } catch (error) { await logCommunication(`Software check error: ${error.message}`, 'error'); progressBar("0", "Error checking software: " + error.message, "#ff6666"); await closePort(); } } async function fetchSoftwareOptions() { try { // Call software_update API to get options const updateUrl = link + "/v2/software_update/sn=" + deviceSerialNumber + (deviceVersion ? "&version=" + deviceVersion : "") + (deviceHwVersion ? "&hw_version=" + deviceHwVersion : ""); console.log("Fetching options from:", updateUrl); await logCommunication(`Fetching software options for SN: ${deviceSerialNumber}`, 'sent'); const updateResponse = await fetch(updateUrl, { method: "GET", headers: { "Authorization": "Bearer " + document.getElementById("servicetoken").textContent, "Content-Type": "application/json" } }); const options = await updateResponse.json(); console.log("Software options:", options); await logCommunication(`Software options response: ${JSON.stringify(options)}`, 'received'); if (options.error) { document.getElementById("softwareCheckStatus").style.display = "none"; document.getElementById("noUpdatesMessage").style.display = "block"; progressBar("100", "No software updates available", "#04AA6D"); await closePort(); return; } // Display options in table displaySoftwareOptions(options); document.getElementById("softwareCheckStatus").style.display = "none"; document.getElementById("softwareOptions").style.display = "block"; progressBar("100", "Software options loaded", "#04AA6D"); } catch (error) { await logCommunication(`Software options error: ${error.message}`, 'error'); progressBar("0", "Error loading options: " + error.message, "#ff6666"); await closePort(); } } function displaySoftwareOptions(options) { const grid = document.getElementById("softwareOptionsGrid"); grid.innerHTML = ""; options.forEach((option, index) => { const price = parseFloat(option.price); const isFree = price === 0; const isCurrent = option.is_current === true || option.is_current === 1; // Create card with gradient background const card = document.createElement("div"); card.style.cssText = ` background: ${isCurrent ? 'linear-gradient(135deg, #f5f5f5 0%, #e8e8e8 100%)' : 'linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%)'}; border-radius: 4px; padding: 25px 20px; transition: all 0.3s ease; display: flex; flex-direction: column; position: relative; overflow: visible; transform: translateY(0px); box-shadow: ${isCurrent ? '0 4px 12px rgba(0,0,0,0.08)' : '0 8px 20px rgba(0,0,0,0.12)'}; opacity: ${isCurrent ? '0.7' : '1'}; pointer-events: ${isCurrent ? 'none' : 'auto'}; min-height: 320px; `; if (!isCurrent) { card.onmouseenter = () => { card.style.transform = 'translateY(-8px) scale(1.02)'; card.style.boxShadow = '0 12px 28px rgba(0,0,0,0.2)'; card.style.borderColor = isFree ? '#038f5a' : '#FF4500'; }; card.onmouseleave = () => { card.style.transform = 'translateY(0) scale(1)'; card.style.boxShadow = '0 8px 20px rgba(0,0,0,0.12)'; card.style.borderColor = isFree ? '#04AA6D' : '#FF6B35'; }; } // Badge for current/free/paid - VISIBLE const badge = document.createElement("div"); badge.style.cssText = ` position: absolute; top: -10px; right: 20px; background: ${isCurrent ? '#6c757d' : (isFree ? 'linear-gradient(135deg, #04AA6D 0%, #038f5a 100%)' : 'linear-gradient(135deg, #FF6B35 0%, #FF4500 100%)')}; color: white; padding: 8px 16px; border-radius: 20px; font-size: 11px; font-weight: 700; letter-spacing: 0.5px; text-transform: uppercase; box-shadow: 0 4px 12px rgba(0,0,0,0.15); `; if (isCurrent) { badge.textContent = "INSTALLED"; } else if (isFree) { badge.textContent = "FREE"; } else { badge.textContent = "PREMIUM"; } card.appendChild(badge); // Name with icon const name = document.createElement("h4"); name.style.cssText = ` margin: 0 0 12px 0; color: #333; font-size: 22px; font-weight: 700; `; name.innerHTML = `${option.name || "Software Update"}`; card.appendChild(name); // Version with enhanced styling const version = document.createElement("div"); version.style.cssText = ` color: #666; font-size: 14px; margin-bottom: 15px; display: flex; align-items: center; gap: 6px; `; version.innerHTML = ` Version: ${option.version || "N/A"}`; card.appendChild(version); // Description with preserved newlines const descContainer = document.createElement("div"); descContainer.style.cssText = ` color: #555; font-size: 13px; line-height: 1.7; margin: 0 0 20px 0; flex-grow: 1; white-space: pre-line; `; descContainer.textContent = option.description || "No description available"; card.appendChild(descContainer); // Price section const priceSection = document.createElement("div"); priceSection.style.cssText = ` border-top: 2px solid ${isFree ? '#04AA6D20' : '#FF6B3520'}; padding-top: 20px; margin-top: auto; `; const priceText = document.createElement("div"); priceText.style.cssText = ` font-size: ${isCurrent ? '18px' : '28px'}; font-weight: ${isCurrent ? '600' : '800'}; color: ${isCurrent ? '#6c757d' : (isFree ? '#04AA6D' : '#FF6B35')}; margin-bottom: 15px; text-align: center; letter-spacing: 0.5px; `; if (isCurrent) { priceText.innerHTML = ' INSTALLED'; } else { priceText.innerHTML = isFree ? 'Free' : `${option.currency || "€"} ${price.toFixed(2)}`; } priceSection.appendChild(priceText); // Action button with gradient for paid const actionBtn = document.createElement("button"); actionBtn.className = "btn"; actionBtn.style.cssText = ` width: 100%; background: ${isCurrent ? '#6c757d' : (isFree ? 'linear-gradient(135deg, #04AA6D 0%, #038f5a 100%)' : 'linear-gradient(135deg, #FF6B35 0%, #FF4500 100%)')}; color: white; border: none; cursor: ${isCurrent ? 'not-allowed' : 'pointer'}; transition: all 0.3s ease; opacity: ${isCurrent ? '0.5' : '1'}; box-shadow: ${isCurrent ? 'none' : '0 4px 12px rgba(0,0,0,0.15)'}; letter-spacing: 0.5px; text-transform: uppercase; `; if (isCurrent) { actionBtn.innerHTML = ' Currently Installed'; actionBtn.disabled = true; } else if (isFree) { actionBtn.innerHTML = ''; actionBtn.onclick = () => selectUpgrade(option); actionBtn.onmouseenter = () => { actionBtn.style.background = 'linear-gradient(135deg, #038f5a 0%, #026b43 100%)'; actionBtn.style.transform = 'translateY(-2px)'; actionBtn.style.boxShadow = '0 6px 16px rgba(0,0,0,0.2)'; }; actionBtn.onmouseleave = () => { actionBtn.style.background = 'linear-gradient(135deg, #04AA6D 0%, #038f5a 100%)'; actionBtn.style.transform = 'translateY(0)'; actionBtn.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)'; }; } else { actionBtn.innerHTML = ''; actionBtn.onclick = () => selectUpgrade(option); actionBtn.onmouseenter = () => { actionBtn.style.background = 'linear-gradient(135deg, #FF4500 0%, #CC3700 100%)'; actionBtn.style.transform = 'translateY(-2px)'; actionBtn.style.boxShadow = '0 6px 16px rgba(255,107,53,0.4)'; }; actionBtn.onmouseleave = () => { actionBtn.style.background = 'linear-gradient(135deg, #FF6B35 0%, #FF4500 100%)'; actionBtn.style.transform = 'translateY(0)'; actionBtn.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)'; }; } priceSection.appendChild(actionBtn); card.appendChild(priceSection); grid.appendChild(card); }); } async function selectUpgrade(option) { const price = parseFloat(option.price || 0); const isFree = price === 0; // If paid upgrade, show payment modal first if (!isFree) { showPaymentModal(option); return; } // Free upgrade - show confirmation modal first showFreeInstallModal(option); } function showFreeInstallModal(option) { // Create modal overlay const modal = document.createElement("div"); modal.id = "freeInstallModal"; modal.style.cssText = ` display: flex; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 1000; align-items: center; justify-content: center; `; // Create modal content const modalContent = document.createElement("div"); modalContent.style.cssText = ` background: white; border-radius: 8px; max-width: 500px; width: 90%; max-height: 90vh; overflow-y: auto; margin: 20px; box-shadow: 0 10px 40px rgba(0,0,0,0.3); `; modalContent.innerHTML = `

Confirm Software Installation

${option.name || "Software Update"}

Version: ${option.version || "N/A"}

${option.description || ""}

`; modal.appendChild(modalContent); document.body.appendChild(modal); // Prefill form with customer data from sessionStorage if available const savedCustomerData = sessionStorage.getItem('customerData'); if (savedCustomerData) { try { const customerData = JSON.parse(savedCustomerData); if (customerData.name) document.getElementById("freeInstallName").value = customerData.name; if (customerData.email) document.getElementById("freeInstallEmail").value = customerData.email; if (customerData.address) document.getElementById("freeInstallAddress").value = customerData.address; if (customerData.city) document.getElementById("freeInstallCity").value = customerData.city; if (customerData.postal) document.getElementById("freeInstallPostal").value = customerData.postal; if (customerData.country) document.getElementById("freeInstallCountry").value = customerData.country; } catch (e) { console.warn('Error parsing saved customer data:', e); } } // Close modal on cancel document.getElementById("cancelFreeInstall").onclick = () => { document.body.removeChild(modal); }; // Handle form submission document.getElementById("freeInstallForm").onsubmit = async (e) => { e.preventDefault(); const formData = new FormData(e.target); const customerData = { name: formData.get("name"), email: formData.get("email"), address: formData.get("address"), city: formData.get("city"), postal: formData.get("postal"), country: formData.get("country"), version_id: option.version_id, price: 0, currency: option.currency || "€" }; // Save customer data to sessionStorage for future use sessionStorage.setItem('customerData', JSON.stringify({ name: customerData.name, email: customerData.email, address: customerData.address, city: customerData.city, postal: customerData.postal, country: customerData.country })); // Close modal document.body.removeChild(modal); // Proceed to download and install with customer data await downloadAndInstallSoftware(option, customerData); }; } function showPaymentModal(option) { const price = parseFloat(option.price || 0); const currency = option.currency || "€"; // Create modal overlay const modal = document.createElement("div"); modal.id = "paymentModal"; modal.style.cssText = ` display: flex; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 1000; align-items: center; justify-content: center; `; // Create modal content const modalContent = document.createElement("div"); modalContent.style.cssText = ` background: white; border-radius: 8px; max-width: 500px; width: 90%; max-height: 90vh; overflow-y: auto; margin: 20px; box-shadow: 0 10px 40px rgba(0,0,0,0.3); `; modalContent.innerHTML = `

Purchase Software Upgrade

${option.name || "Software Update"}

Version: ${option.version || "N/A"}

${option.description || ""}

${currency} ${price.toFixed(2)}
`; modal.appendChild(modalContent); document.body.appendChild(modal); // Prefill form with customer data from sessionStorage if available const savedCustomerData = sessionStorage.getItem('customerData'); if (savedCustomerData) { try { const customerData = JSON.parse(savedCustomerData); if (customerData.name) document.getElementById("paymentName").value = customerData.name; if (customerData.email) document.getElementById("paymentEmail").value = customerData.email; if (customerData.address) document.getElementById("paymentAddress").value = customerData.address; if (customerData.city) document.getElementById("paymentCity").value = customerData.city; if (customerData.postal) document.getElementById("paymentPostal").value = customerData.postal; if (customerData.country) document.getElementById("paymentCountry").value = customerData.country; } catch (e) { console.warn('Error parsing saved customer data:', e); } } // Close modal on cancel document.getElementById("cancelPayment").onclick = () => { document.body.removeChild(modal); }; // Handle form submission document.getElementById("paymentForm").onsubmit = async (e) => { e.preventDefault(); const formData = new FormData(e.target); const paymentMethod = formData.get("payment_method"); // Auto-determine payment provider based on payment method let paymentProvider = 'mollie'; // default if (paymentMethod === 'paypal') { paymentProvider = 'paypal'; } else if (paymentMethod === 'credit_card' || paymentMethod === 'bank_transfer') { paymentProvider = 'mollie'; } const paymentData = { name: formData.get("name"), email: formData.get("email"), address: formData.get("address"), city: formData.get("city"), postal: formData.get("postal"), country: formData.get("country"), payment_method: paymentMethod, payment_provider: paymentProvider, version_id: option.version_id, price: price, currency: currency }; // Save customer data to sessionStorage for future use sessionStorage.setItem('customerData', JSON.stringify({ name: paymentData.name, email: paymentData.email, address: paymentData.address, city: paymentData.city, postal: paymentData.postal, country: paymentData.country })); await processPayment(paymentData, option, modal); }; } async function logSoftwareInstallationToHistory(option, customerData = null) { try { const serviceToken = document.getElementById("servicetoken")?.innerHTML || ''; // Create payload matching the structure expected by v2/history const payload = { version: option.version, version_id: option.version_id, name: option.name, description: option.description, HW: deviceHwVersion, HEX_FW: option.version, price: option.price || 0, currency: option.currency || "€", installation_source: 'softwaretool' }; // Add customer data if provided if (customerData) { payload.customer = { name: customerData.name, email: customerData.email, address: customerData.address, city: customerData.city, postal: customerData.postal, country: customerData.country }; } const historyData = { sn: deviceSerialNumber, type: 'firmware', sn_service: 'Portal', payload: payload }; const url = link + '/v2/history'; const bearer = 'Bearer ' + serviceToken; console.log("Logging installation to history:", historyData); await logCommunication(`Logging software installation to history: ${JSON.stringify(historyData)}`, 'sent'); const response = await fetch(url, { method: 'POST', withCredentials: true, credentials: 'include', headers: { 'Authorization': bearer, 'Content-Type': 'application/json' }, body: JSON.stringify(historyData) }); if (!response.ok) { console.warn('Failed to log to history:', response.status); await logCommunication(`Failed to log to history: ${response.status}`, 'error'); } else { const result = await response.json(); console.log("History logged successfully:", result); await logCommunication(`History logged successfully: ${JSON.stringify(result)}`, 'received'); } } catch (error) { console.warn('Error logging to history:', error); await logCommunication(`Error logging to history: ${error.message}`, 'error'); // Don't block the installation if history logging fails } } async function processPayment(paymentData, option, modal) { try { progressBar("10", "Processing payment...", "#04AA6D"); // SECURITY: Only send serial_number and version_id // Server will calculate price to prevent tampering const paymentRequest = { serial_number: deviceSerialNumber, version_id: option.version_id, payment_method: paymentData.payment_method, payment_provider: paymentData.payment_provider, user_data: paymentData // name, email, address, etc. }; // Debug logging if (typeof DEBUG !== 'undefined' && DEBUG) { console.log("=== DEBUG: Payment Request ==="); console.log("Serial Number:", deviceSerialNumber); console.log("Version ID:", option.version_id); console.log("Payment Method:", paymentData.payment_method); console.log("Payment Provider:", paymentData.payment_provider); console.log("User Data:", paymentData); console.log("Request payload:", paymentRequest); } await logCommunication(`Payment initiated for version ${option.version_id} via ${paymentData.payment_provider}`, 'sent'); // Call payment API (handles both Mollie and PayPal) const response = await fetch(link + "/v2/payment", { method: "POST", headers: { "Content-Type": "application/json", "Authorization": "Bearer " + document.getElementById("servicetoken").textContent }, body: JSON.stringify(paymentRequest) }); if (!response.ok) { const errorData = await response.json(); if (typeof DEBUG !== 'undefined' && DEBUG) { console.error("DEBUG: Payment API error:", errorData); } throw new Error(errorData.error || "Failed to create payment"); } const result = await response.json(); if (typeof DEBUG !== 'undefined' && DEBUG) { console.log("=== DEBUG: Payment Response ==="); console.log("Result:", result); console.log("Checkout URL:", result.checkout_url); console.log("Payment ID:", result.payment_id); } if (result.checkout_url) { await logCommunication(`Redirecting to ${paymentData.payment_provider} payment: ${result.payment_id}`, 'sent'); if (typeof DEBUG !== 'undefined' && DEBUG) { console.log(`DEBUG: Redirecting to ${paymentData.payment_provider} checkout...`); } // Close modal before redirect document.body.removeChild(modal); // Redirect to payment checkout page window.location.href = result.checkout_url; } else { throw new Error(result.error || "No checkout URL received"); } } catch (error) { if (typeof DEBUG !== 'undefined' && DEBUG) { console.error("DEBUG: Payment processing error:", error); } await logCommunication(`Payment error: ${error.message}`, 'error'); progressBar("0", "Payment failed: " + error.message, "#ff6666"); alert("Payment failed: " + error.message); } } async function downloadAndInstallSoftware(option, customerData = null) { //+++++++++++++++++++++++++++++++++++++++++++++++++++++ // SECURITY: Check if returning from payment - verify serial number //+++++++++++++++++++++++++++++++++++++++++++++++++++++ const urlParams = new URLSearchParams(window.location.search); const paymentId = urlParams.get('payment_id'); if (paymentId) { try { // Verify serial number matches payment const response = await fetch(link + `/v2/payment?payment_id=${paymentId}`, { method: "GET", headers: { "Authorization": "Bearer " + document.getElementById("servicetoken").textContent } }); if (response.ok) { const paymentData = await response.json(); // Check if device serial number matches the one from payment if (paymentData.serial_number && paymentData.serial_number !== deviceSerialNumber) { const confirmed = confirm( `WARNING: Different device detected!\n\n` + `License was created for device: ${paymentData.serial_number}\n` + `Currently connected device: ${deviceSerialNumber}\n\n` + `The license is already applied to the original device. ` + `Do you want to continue with this device anyway?` ); if (!confirmed) { progressBar("0", "Upload canceled by user", "#ff6666"); await logCommunication('Upload canceled - serial number mismatch', 'error'); return; } await logCommunication(`WARNING: Serial number mismatch detected (payment: ${paymentData.serial_number}, device: ${deviceSerialNumber})`, 'warning'); } } } catch (error) { console.error("Error verifying payment:", error); // Don't block if verification fails - log and continue await logCommunication(`Payment verification failed: ${error.message}`, 'warning'); } } selectedSoftwareUrl = option.source; if (!selectedSoftwareUrl) { alert("Error: No download URL available"); await logCommunication('No download URL available for selected option', 'error'); return; } console.log("Download URL:", selectedSoftwareUrl); await logCommunication(`Downloading software from: ${selectedSoftwareUrl}`, 'sent'); // Log to v2/history before starting download (with customer data if provided) await logSoftwareInstallationToHistory(option, customerData); try { // Download the software file progressBar("10", "Downloading software...", "#04AA6D"); const response = await fetch(selectedSoftwareUrl, { method: "GET", headers: { "Authorization": "Bearer " + document.getElementById("servicetoken").textContent } }); if (!response.ok) { // Try to parse error message const errorText = await response.text(); let errorMessage = `Download failed: ${response.status}`; try { const errorJson = JSON.parse(errorText); errorMessage = errorJson.message || errorJson.error || errorMessage; } catch (e) { // Not JSON, use status text } console.error("Download failed:", response.status, errorText); await logCommunication(`Download failed: ${response.status} - ${errorText}`, 'error'); progressBar("0", "Error: " + errorMessage, "#ff6666"); alert("Download failed: " + errorMessage); return; } // Get the blob const blob = await response.blob(); progressBar("50", "Download complete, preparing installation...", "#04AA6D"); await logCommunication(`Software downloaded successfully, size: ${blob.size} bytes`, 'received'); // Create a blob URL for the downloaded file const blobUrl = URL.createObjectURL(blob); // Set global variables expected by upload.js window.firmwarelocation = blobUrl; window.upgraded_version = option.version || ""; // DEBUG MODE: Don't auto-trigger upload, let user manually test if (typeof DEBUG !== 'undefined' && DEBUG) { // Show upload section and button for manual testing document.getElementById("uploadSection").style.display = "block"; const uploadBtn = document.getElementById("uploadSoftware"); uploadBtn.style.display = "block"; uploadBtn.disabled = false; progressBar("100", "DEBUG MODE: Download complete. Click 'Install Software' button to test upload.js manually", "#ff9800"); console.log("=== DEBUG MODE: Manual Upload Test ==="); console.log("Firmware downloaded successfully"); console.log("Blob size:", blob.size, "bytes"); console.log("Blob URL:", blobUrl); console.log("window.firmwarelocation =", window.firmwarelocation); console.log("window.upgraded_version =", window.upgraded_version); console.log("Upload button is now visible and enabled"); console.log("Click the 'Install Software' button to test if upload.js can handle the file"); alert("DEBUG MODE: Download complete!\n\nBlob size: " + blob.size + " bytes\n\nClick the 'Install Software' button to test upload.js"); } else { // PRODUCTION MODE: Hide button and show installation in progress document.getElementById("uploadSection").style.display = "block"; const uploadBtn = document.getElementById("uploadSoftware"); uploadBtn.style.display = "none"; // Hide device version information during installation const softwareOptions = document.getElementById("softwareOptions"); if (softwareOptions) { softwareOptions.style.display = "none"; } // Create installation status indicator const installStatus = document.createElement("div"); installStatus.id = "installationStatus"; installStatus.style.cssText = ` text-align: center; padding: 20px; background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); border-radius: 8px; margin: 10px 0; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); `; installStatus.innerHTML = `

Installing Software...

Please keep your device connected and do not close this page

`; // Insert status before the hidden upload section document.getElementById("uploadSection").parentNode.insertBefore(installStatus, document.getElementById("uploadSection")); progressBar("60", "Starting automatic installation...", "#04AA6D"); // Enable the upload button and automatically click it setTimeout(() => { uploadBtn.disabled = false; // Start monitoring for completion if (typeof startUploadMonitoring === 'function') { startUploadMonitoring(); } uploadBtn.click(); }, 1000); } } catch (error) { await logCommunication(`Download error: ${error.message}`, 'error'); progressBar("0", "Error downloading software: " + error.message, "#ff6666"); alert("Error: " + error.message); } }