// Base64 encoding/decoding utilities const b64 = { encode: array => btoa(String.fromCharCode.apply(null, array)), decode: str => Uint8Array.from(atob(str), c => c.charCodeAt(0)) }; async function generateKeyPair() { const keyPair = await window.crypto.subtle.generateKey( { name: 'X25519', namedCurve: 'X25519', }, true, ['deriveKey', 'deriveBits'] ); const privateKey = await window.crypto.subtle.exportKey('raw', keyPair.privateKey); const publicKey = await window.crypto.subtle.exportKey('raw', keyPair.publicKey); return { privateKey: b64.encode(new Uint8Array(privateKey)), publicKey: b64.encode(new Uint8Array(publicKey)) }; } document.addEventListener('DOMContentLoaded', async function() { const form = document.getElementById('subscription-form'); const slider = document.getElementById('duration-slider'); const durationDisplay = document.getElementById('duration-display'); const priceDisplay = document.getElementById('price-display'); const presetButtons = document.querySelectorAll('.duration-preset'); const userIdInput = document.getElementById('user-id'); const publicKeyInput = document.getElementById('public-key'); const regenerateButton = document.getElementById('regenerate-keys'); let currentKeyPair = null; function formatDuration(hours) { if (hours < 24) return `${hours} hour${hours === 1 ? '' : 's'}`; if (hours < 168) return `${hours / 24} day${hours === 24 ? '' : 's'}`; if (hours < 720) return `${Math.floor(hours / 168)} week${hours === 168 ? '' : 's'}`; return `${Math.floor(hours / 720)} month${hours === 720 ? '' : 's'}`; } async function generateNewKeys() { try { currentKeyPair = await generateKeyPair(); publicKeyInput.value = currentKeyPair.publicKey; // Save private key to localStorage const keyData = { privateKey: currentKeyPair.privateKey, publicKey: currentKeyPair.publicKey, createdAt: new Date().toISOString() }; localStorage.setItem(`vpn_keys_${userIdInput.value}`, JSON.stringify(keyData)); } catch (error) { console.error('Failed to generate keys:', error); alert('Failed to generate WireGuard keys. Please try again.'); } } async function initializeForm() { // Generate user ID userIdInput.value = crypto.randomUUID(); // Generate initial keys await generateNewKeys(); } async function updatePrice(hours) { try { const response = await fetch('/api/calculate-price', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ hours: parseInt(hours) }) }); const data = await response.json(); priceDisplay.textContent = data.price; durationDisplay.textContent = formatDuration(hours); } catch (error) { console.error('Error calculating price:', error); } } // Event listeners slider.addEventListener('input', () => updatePrice(slider.value)); regenerateButton.addEventListener('click', generateNewKeys); presetButtons.forEach(button => { button.addEventListener('click', (e) => { const hours = e.target.dataset.hours; slider.value = hours; updatePrice(hours); }); }); form.addEventListener('submit', async (e) => { e.preventDefault(); if (!currentKeyPair) { alert('No keys generated. Please refresh the page.'); return; } try { const response = await fetch('/create-invoice', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ duration: parseInt(slider.value), userId: userIdInput.value, publicKey: currentKeyPair.publicKey }) }); if (!response.ok) { throw new Error('Failed to create invoice'); } const data = await response.json(); window.location.href = data.checkout_url; } catch (error) { console.error('Error creating invoice:', error); alert('Failed to create payment invoice. Please try again.'); } }); // Initialize the form await initializeForm(); // Initial price calculation updatePrice(slider.value); });