137 lines
4.7 KiB
JavaScript
137 lines
4.7 KiB
JavaScript
// 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);
|
|
}); |