Moved static files to static/ folder

This commit is contained in:
Ivan Golikov 2025-01-09 13:47:59 +01:00
parent 0965ffea41
commit d1f29825d3
5 changed files with 2 additions and 2 deletions

79
static/app.js Normal file
View file

@ -0,0 +1,79 @@
import config from './config.js';
import { generateKey, encryptData, decryptData, exportKey, importKey } from './crypto.js';
const textarea = document.getElementById('secretInput');
const shareButton = document.getElementById('shareButton');
const spinner = document.getElementById('spinner');
async function handleShare() {
if (!textarea.value.trim()) return;
try {
spinner.classList.remove('hidden');
// Generate encryption key and encrypt data
const key = await generateKey();
const encryptedData = await encryptData(textarea.value, key);
// Send encrypted data to server
const response = await fetch(`${config.API_BASE_URL}/secret`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ data: encryptedData })
});
const { key: secretKey } = await response.json();
const exportedKey = await exportKey(key);
// Create sharing URL
const combinedData = `${secretKey}::${exportedKey}`;
const encodedData = btoa(combinedData);
const shareUrl = `${window.location.origin}/${encodedData}`;
textarea.value = shareUrl;
} catch (error) {
console.error('Sharing failed:', error);
} finally {
spinner.classList.add('hidden');
}
}
async function handlePageLoad() {
const path = window.location.pathname.substring(1);
if (!path) return;
try {
// Try to decode the URL
const decodedData = atob(path);
const [secretKey, encryptionKeyData] = decodedData.split('::');
if (!secretKey || !encryptionKeyData) return;
spinner.classList.remove('hidden');
// Fetch the secret
const response = await fetch(`${config.API_BASE_URL}/secret/${secretKey}`);
if (response.status === 404) {
textarea.value = "Oops, your secret could not be found. Have you already retrieved it?";
return;
}
const { data: encryptedData } = await response.json();
// Decrypt the data
const key = await importKey(encryptionKeyData);
const decryptedData = await decryptData(encryptedData, key);
textarea.value = decryptedData;
} catch (error) {
console.error('Loading secret failed:', error);
} finally {
spinner.classList.add('hidden');
}
}
shareButton.addEventListener('click', handleShare);
window.addEventListener('load', handlePageLoad);

5
static/config.js Normal file
View file

@ -0,0 +1,5 @@
const config = {
API_BASE_URL: 'http://localhost:8000'
};
export default config;

69
static/crypto.js Normal file
View file

@ -0,0 +1,69 @@
export async function generateKey() {
return await window.crypto.subtle.generateKey(
{
name: "AES-GCM",
length: 256
},
true,
["encrypt", "decrypt"]
);
}
export async function encryptData(data, key) {
const iv = window.crypto.getRandomValues(new Uint8Array(12));
const encodedData = new TextEncoder().encode(data);
const encryptedData = await window.crypto.subtle.encrypt(
{
name: "AES-GCM",
iv: iv
},
key,
encodedData
);
const encryptedArray = new Uint8Array(encryptedData);
const resultArray = new Uint8Array(iv.length + encryptedArray.length);
resultArray.set(iv);
resultArray.set(encryptedArray, iv.length);
return btoa(String.fromCharCode(...resultArray));
}
export async function decryptData(encryptedData, key) {
try {
const data = Uint8Array.from(atob(encryptedData), c => c.charCodeAt(0));
const iv = data.slice(0, 12);
const ciphertext = data.slice(12);
const decrypted = await window.crypto.subtle.decrypt(
{
name: "AES-GCM",
iv: iv
},
key,
ciphertext
);
return new TextDecoder().decode(decrypted);
} catch (error) {
console.error('Decryption failed:', error);
throw error;
}
}
export async function exportKey(key) {
const exported = await window.crypto.subtle.exportKey("raw", key);
return btoa(String.fromCharCode(...new Uint8Array(exported)));
}
export async function importKey(keyData) {
const keyBytes = Uint8Array.from(atob(keyData), c => c.charCodeAt(0));
return await window.crypto.subtle.importKey(
"raw",
keyBytes,
"AES-GCM",
true,
["encrypt", "decrypt"]
);
}

80
static/styles.css Normal file
View file

@ -0,0 +1,80 @@
body {
margin: 0;
padding: 0;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background-color: #2f2d3c;
font-family: Arial, sans-serif;
}
.container {
position: relative;
width: 100%;
max-width: 600px;
padding: 20px;
}
.form-container {
background-color: rgba(255, 255, 255, 0.1);
border-radius: 10px;
padding: 20px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
textarea {
width: 100%;
min-height: 150px;
padding: 15px;
border: none;
border-radius: 5px;
background-color: rgba(255, 255, 255, 0.1);
color: white;
font-size: 16px;
resize: vertical;
margin-bottom: 15px;
box-sizing: border-box;
}
textarea::placeholder {
color: rgba(255, 255, 255, 0.7);
}
button {
float: right;
padding: 10px 30px;
background-color: #00e5c7;
border: none;
border-radius: 5px;
color: white;
font-size: 16px;
cursor: pointer;
transition: background-color 0.3s;
}
button:hover {
background-color: #00c4aa;
}
.spinner {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 50px;
height: 50px;
border: 5px solid rgba(255, 255, 255, 0.1);
border-top: 5px solid #00e5c7;
border-radius: 50%;
animation: spin 1s linear infinite;
}
.hidden {
display: none;
}
@keyframes spin {
0% { transform: translate(-50%, -50%) rotate(0deg); }
100% { transform: translate(-50%, -50%) rotate(360deg); }
}