799 lines
26 KiB
HTML
799 lines
26 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Decrypt Secure Support Reply</title>
|
|
<style>
|
|
* {
|
|
box-sizing: border-box;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
|
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
|
min-height: 100vh;
|
|
padding: 2rem;
|
|
color: #e0e0e0;
|
|
}
|
|
|
|
.container {
|
|
max-width: 800px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
h1 {
|
|
text-align: center;
|
|
margin-bottom: 0.5rem;
|
|
font-size: 1.8rem;
|
|
background: linear-gradient(90deg, #00d9ff, #00ff94);
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
background-clip: text;
|
|
}
|
|
|
|
.subtitle {
|
|
text-align: center;
|
|
color: #888;
|
|
margin-bottom: 2rem;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.card {
|
|
background: rgba(255,255,255,0.05);
|
|
border-radius: 16px;
|
|
padding: 2rem;
|
|
margin-bottom: 1.5rem;
|
|
border: 1px solid rgba(255,255,255,0.1);
|
|
backdrop-filter: blur(10px);
|
|
}
|
|
|
|
.card h2 {
|
|
font-size: 1.1rem;
|
|
margin-bottom: 1rem;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.card h2 .icon {
|
|
font-size: 1.3rem;
|
|
}
|
|
|
|
.input-group {
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
label {
|
|
display: block;
|
|
margin-bottom: 0.5rem;
|
|
color: #aaa;
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
textarea, input[type="password"], input[type="text"] {
|
|
width: 100%;
|
|
padding: 0.8rem 1rem;
|
|
border: 1px solid rgba(255,255,255,0.1);
|
|
border-radius: 8px;
|
|
background: rgba(0,0,0,0.3);
|
|
color: #fff;
|
|
font-family: 'Monaco', 'Menlo', monospace;
|
|
font-size: 0.85rem;
|
|
resize: vertical;
|
|
}
|
|
|
|
textarea:focus, input:focus {
|
|
outline: none;
|
|
border-color: #00d9ff;
|
|
box-shadow: 0 0 0 3px rgba(0, 217, 255, 0.1);
|
|
}
|
|
|
|
textarea.encrypted {
|
|
min-height: 120px;
|
|
font-size: 0.75rem;
|
|
word-break: break-all;
|
|
}
|
|
|
|
.password-row {
|
|
display: flex;
|
|
gap: 1rem;
|
|
align-items: flex-end;
|
|
}
|
|
|
|
.password-row .input-group {
|
|
flex: 1;
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
button {
|
|
padding: 0.8rem 2rem;
|
|
border: none;
|
|
border-radius: 8px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
button.primary {
|
|
background: linear-gradient(135deg, #00d9ff 0%, #00ff94 100%);
|
|
color: #000;
|
|
}
|
|
|
|
button.primary:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 15px rgba(0, 217, 255, 0.4);
|
|
}
|
|
|
|
button.primary:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
transform: none;
|
|
}
|
|
|
|
button.secondary {
|
|
background: rgba(255,255,255,0.1);
|
|
color: #fff;
|
|
border: 1px solid rgba(255,255,255,0.2);
|
|
}
|
|
|
|
button.secondary:hover {
|
|
background: rgba(255,255,255,0.15);
|
|
}
|
|
|
|
.hint-banner {
|
|
background: rgba(255, 193, 7, 0.1);
|
|
border: 1px solid rgba(255, 193, 7, 0.3);
|
|
border-radius: 8px;
|
|
padding: 0.8rem 1rem;
|
|
margin-bottom: 1rem;
|
|
display: none;
|
|
}
|
|
|
|
.hint-banner.visible {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.hint-banner .hint-icon {
|
|
font-size: 1.2rem;
|
|
}
|
|
|
|
.hint-banner .hint-text {
|
|
color: #ffc107;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.message-container {
|
|
display: none;
|
|
}
|
|
|
|
.message-container.visible {
|
|
display: block;
|
|
}
|
|
|
|
.message-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: flex-start;
|
|
margin-bottom: 1rem;
|
|
padding-bottom: 1rem;
|
|
border-bottom: 1px solid rgba(255,255,255,0.1);
|
|
}
|
|
|
|
.message-from {
|
|
font-weight: 600;
|
|
color: #00d9ff;
|
|
}
|
|
|
|
.message-date {
|
|
font-size: 0.8rem;
|
|
color: #888;
|
|
}
|
|
|
|
.message-subject {
|
|
font-size: 1.2rem;
|
|
font-weight: 600;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.message-body {
|
|
line-height: 1.7;
|
|
white-space: pre-wrap;
|
|
}
|
|
|
|
.attachments {
|
|
margin-top: 1.5rem;
|
|
padding-top: 1rem;
|
|
border-top: 1px solid rgba(255,255,255,0.1);
|
|
}
|
|
|
|
.attachments h3 {
|
|
font-size: 0.9rem;
|
|
color: #888;
|
|
margin-bottom: 0.8rem;
|
|
}
|
|
|
|
.attachment-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.8rem;
|
|
padding: 0.6rem 1rem;
|
|
background: rgba(0,0,0,0.2);
|
|
border-radius: 8px;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.attachment-icon {
|
|
font-size: 1.5rem;
|
|
}
|
|
|
|
.attachment-info {
|
|
flex: 1;
|
|
}
|
|
|
|
.attachment-name {
|
|
font-weight: 500;
|
|
}
|
|
|
|
.attachment-meta {
|
|
font-size: 0.75rem;
|
|
color: #888;
|
|
}
|
|
|
|
.attachment-download {
|
|
padding: 0.4rem 0.8rem;
|
|
font-size: 0.8rem;
|
|
}
|
|
|
|
.reply-key-banner {
|
|
background: rgba(0, 217, 255, 0.1);
|
|
border: 1px solid rgba(0, 217, 255, 0.3);
|
|
border-radius: 8px;
|
|
padding: 1rem;
|
|
margin-top: 1.5rem;
|
|
display: none;
|
|
}
|
|
|
|
.reply-key-banner.visible {
|
|
display: block;
|
|
}
|
|
|
|
.reply-key-banner h4 {
|
|
font-size: 0.9rem;
|
|
margin-bottom: 0.5rem;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.reply-key-banner p {
|
|
font-size: 0.8rem;
|
|
color: #aaa;
|
|
margin-bottom: 0.8rem;
|
|
}
|
|
|
|
.reply-key-value {
|
|
font-family: 'Monaco', 'Menlo', monospace;
|
|
font-size: 0.7rem;
|
|
background: rgba(0,0,0,0.3);
|
|
padding: 0.5rem;
|
|
border-radius: 4px;
|
|
word-break: break-all;
|
|
}
|
|
|
|
.error-banner {
|
|
background: rgba(255, 82, 82, 0.1);
|
|
border: 1px solid rgba(255, 82, 82, 0.3);
|
|
border-radius: 8px;
|
|
padding: 1rem;
|
|
margin-bottom: 1rem;
|
|
display: none;
|
|
color: #ff5252;
|
|
}
|
|
|
|
.error-banner.visible {
|
|
display: block;
|
|
}
|
|
|
|
.status-indicator {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
font-size: 0.85rem;
|
|
padding: 0.5rem 0;
|
|
}
|
|
|
|
.status-indicator .dot {
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
}
|
|
|
|
.status-indicator.loading .dot {
|
|
background: #ffc107;
|
|
animation: pulse 1s infinite;
|
|
}
|
|
|
|
.status-indicator.ready .dot {
|
|
background: #00ff94;
|
|
}
|
|
|
|
.status-indicator.error .dot {
|
|
background: #ff5252;
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0%, 100% { opacity: 1; }
|
|
50% { opacity: 0.5; }
|
|
}
|
|
|
|
.demo-section {
|
|
border-top: 1px dashed rgba(255,255,255,0.1);
|
|
padding-top: 1.5rem;
|
|
margin-top: 1.5rem;
|
|
}
|
|
|
|
.demo-section h3 {
|
|
font-size: 0.9rem;
|
|
color: #888;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.example-messages {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.example-messages button {
|
|
padding: 0.5rem 1rem;
|
|
font-size: 0.8rem;
|
|
}
|
|
|
|
.nav-links {
|
|
display: flex;
|
|
justify-content: center;
|
|
gap: 1rem;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.nav-links a {
|
|
color: #00d9ff;
|
|
text-decoration: none;
|
|
font-size: 0.85rem;
|
|
padding: 0.5rem 1rem;
|
|
border-radius: 20px;
|
|
background: rgba(0, 217, 255, 0.1);
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.nav-links a:hover {
|
|
background: rgba(0, 217, 255, 0.2);
|
|
}
|
|
|
|
.nav-links a.active {
|
|
background: rgba(0, 217, 255, 0.3);
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h1>Secure Support Reply</h1>
|
|
<p class="subtitle">Decrypt password-protected messages from support</p>
|
|
|
|
<nav class="nav-links">
|
|
<a href="index.html">Form Encryption</a>
|
|
<a href="support-reply.html" class="active">Decrypt Messages</a>
|
|
<a href="media-player.html">Media Player</a>
|
|
<a href="artist-portal.html">Artist Portal</a>
|
|
</nav>
|
|
|
|
<div id="wasm-status" class="status-indicator loading">
|
|
<span class="dot"></span>
|
|
<span>Loading encryption module...</span>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2><span class="icon">📨</span> Encrypted Message</h2>
|
|
|
|
<div class="input-group">
|
|
<label for="encrypted-message">Paste the encrypted message you received:</label>
|
|
<textarea id="encrypted-message" class="encrypted" placeholder="U01TRy4uLg=="></textarea>
|
|
</div>
|
|
|
|
<div id="hint-banner" class="hint-banner">
|
|
<span class="hint-icon">💡</span>
|
|
<span class="hint-text">Password hint: <strong id="hint-text"></strong></span>
|
|
</div>
|
|
|
|
<div id="error-banner" class="error-banner"></div>
|
|
|
|
<div class="password-row">
|
|
<div class="input-group">
|
|
<label for="password">Password:</label>
|
|
<input type="password" id="password" placeholder="Enter your password">
|
|
</div>
|
|
<button id="decrypt-btn" class="primary" disabled>Decrypt</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="message-container" class="card message-container">
|
|
<h2><span class="icon">📬</span> Decrypted Message</h2>
|
|
|
|
<div class="message-header">
|
|
<div>
|
|
<div class="message-from" id="msg-from">Support Team</div>
|
|
<div id="msg-subject" class="message-subject"></div>
|
|
</div>
|
|
<div class="message-date" id="msg-date"></div>
|
|
</div>
|
|
|
|
<div class="message-body" id="msg-body"></div>
|
|
|
|
<div id="attachments-container" class="attachments" style="display: none;">
|
|
<h3>Attachments</h3>
|
|
<div id="attachments-list"></div>
|
|
</div>
|
|
|
|
<div id="reply-key-banner" class="reply-key-banner">
|
|
<h4><span>🔐</span> Authenticated Reply Key</h4>
|
|
<p>This message includes a public key for secure replies. Use this to encrypt your response:</p>
|
|
<div class="reply-key-value" id="reply-key"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="demo-section">
|
|
<h3>Demo: Try with sample messages</h3>
|
|
<p style="font-size: 0.85rem; color: #888; margin-bottom: 1rem;">
|
|
Click a button to load a pre-encrypted sample message. All use password: <code style="background: rgba(0,0,0,0.3); padding: 0.2rem 0.4rem; border-radius: 4px;">demo123</code>
|
|
</p>
|
|
<div class="example-messages" id="example-buttons"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="wasm_exec.js"></script>
|
|
<script>
|
|
// Example encrypted messages (will be populated by WASM generation)
|
|
const EXAMPLES = {
|
|
'simple': '',
|
|
'with-attachment': '',
|
|
'with-hint': '',
|
|
'with-reply-key': ''
|
|
};
|
|
|
|
// Store attachment data for downloads
|
|
const attachmentData = new Map();
|
|
|
|
let wasmReady = false;
|
|
|
|
// Initialize WASM
|
|
async function initWasm() {
|
|
const statusEl = document.getElementById('wasm-status');
|
|
|
|
try {
|
|
const go = new Go();
|
|
const result = await WebAssembly.instantiateStreaming(
|
|
fetch('stmf.wasm'),
|
|
go.importObject
|
|
);
|
|
go.run(result.instance);
|
|
|
|
// Wait for BorgSMSG to be ready
|
|
await new Promise((resolve, reject) => {
|
|
const timeout = setTimeout(() => reject(new Error('WASM init timeout')), 5000);
|
|
|
|
if (typeof BorgSMSG !== 'undefined' && BorgSMSG.ready) {
|
|
clearTimeout(timeout);
|
|
resolve();
|
|
return;
|
|
}
|
|
|
|
document.addEventListener('borgstmf:ready', () => {
|
|
clearTimeout(timeout);
|
|
resolve();
|
|
});
|
|
});
|
|
|
|
wasmReady = true;
|
|
updateStatus(statusEl, 'ready', 'Encryption module ready (v' + BorgSMSG.version + ')');
|
|
document.getElementById('decrypt-btn').disabled = false;
|
|
|
|
// Generate example messages
|
|
await generateExamples();
|
|
setupExampleButtons();
|
|
|
|
} catch (err) {
|
|
updateStatus(statusEl, 'error', 'Failed to load: ' + err.message);
|
|
console.error('WASM init error:', err);
|
|
}
|
|
}
|
|
|
|
// Update status indicator safely
|
|
function updateStatus(el, status, message) {
|
|
el.className = 'status-indicator ' + status;
|
|
// Clear and rebuild safely
|
|
while (el.firstChild) el.removeChild(el.firstChild);
|
|
const dot = document.createElement('span');
|
|
dot.className = 'dot';
|
|
const text = document.createElement('span');
|
|
text.textContent = message;
|
|
el.appendChild(dot);
|
|
el.appendChild(text);
|
|
}
|
|
|
|
// Setup example buttons safely
|
|
function setupExampleButtons() {
|
|
const container = document.getElementById('example-buttons');
|
|
const examples = [
|
|
{ key: 'simple', label: 'Simple Message' },
|
|
{ key: 'with-attachment', label: 'With Attachment' },
|
|
{ key: 'with-hint', label: 'With Password Hint' },
|
|
{ key: 'with-reply-key', label: 'With Reply Key' }
|
|
];
|
|
|
|
examples.forEach(ex => {
|
|
const btn = document.createElement('button');
|
|
btn.className = 'secondary';
|
|
btn.textContent = ex.label;
|
|
btn.addEventListener('click', () => loadExample(ex.key));
|
|
container.appendChild(btn);
|
|
});
|
|
}
|
|
|
|
// Generate example encrypted messages
|
|
async function generateExamples() {
|
|
try {
|
|
// Simple message
|
|
EXAMPLES['simple'] = await BorgSMSG.encrypt({
|
|
body: 'Hello! Thank you for contacting our support team.\n\nWe have reviewed your request and are happy to help. Please let us know if you have any other questions.\n\nBest regards,\nThe Support Team',
|
|
subject: 'Re: Your Support Request #12345',
|
|
from: 'support@example.com'
|
|
}, 'demo123');
|
|
|
|
// With attachment
|
|
const fileContent = btoa('This is the content of the attached file.\nIt contains important information.');
|
|
EXAMPLES['with-attachment'] = await BorgSMSG.encrypt({
|
|
body: 'Please find the requested document attached to this message.\n\nThe file contains the information you requested about your account.',
|
|
subject: 'Document Attached',
|
|
from: 'documents@example.com',
|
|
attachments: [{
|
|
name: 'account-details.txt',
|
|
content: fileContent,
|
|
mime: 'text/plain'
|
|
}]
|
|
}, 'demo123');
|
|
|
|
// With password hint
|
|
EXAMPLES['with-hint'] = await BorgSMSG.encrypt({
|
|
body: 'This is a confidential message that requires your password to view.\n\nYour account has been updated as requested.',
|
|
subject: 'Account Update Confirmation',
|
|
from: 'security@example.com'
|
|
}, 'demo123', 'demo + 123');
|
|
|
|
// With reply key
|
|
EXAMPLES['with-reply-key'] = await BorgSMSG.encrypt({
|
|
body: 'This message includes a public key for secure replies.\n\nWhen you reply, use the attached public key to encrypt your response. This ensures only we can read your reply.',
|
|
subject: 'Secure Communication Channel',
|
|
from: 'secure@example.com',
|
|
replyKey: {
|
|
publicKey: 'dGVzdHB1YmxpY2tleWZvcmRlbW9wdXJwb3Nlcw=='
|
|
}
|
|
}, 'demo123');
|
|
|
|
console.log('Example messages generated');
|
|
} catch (err) {
|
|
console.error('Failed to generate examples:', err);
|
|
}
|
|
}
|
|
|
|
// Load example message
|
|
function loadExample(type) {
|
|
const textarea = document.getElementById('encrypted-message');
|
|
textarea.value = EXAMPLES[type];
|
|
checkForHint();
|
|
}
|
|
|
|
// Check for password hint
|
|
async function checkForHint() {
|
|
const encryptedB64 = document.getElementById('encrypted-message').value.trim();
|
|
const hintBanner = document.getElementById('hint-banner');
|
|
const hintText = document.getElementById('hint-text');
|
|
|
|
hintBanner.classList.remove('visible');
|
|
|
|
if (!encryptedB64 || !wasmReady) return;
|
|
|
|
try {
|
|
const info = await BorgSMSG.getInfo(encryptedB64);
|
|
if (info.hint) {
|
|
hintText.textContent = info.hint;
|
|
hintBanner.classList.add('visible');
|
|
}
|
|
} catch (err) {
|
|
// Silently ignore - invalid format
|
|
}
|
|
}
|
|
|
|
// Decrypt message
|
|
async function decryptMessage() {
|
|
const encryptedB64 = document.getElementById('encrypted-message').value.trim();
|
|
const password = document.getElementById('password').value;
|
|
const errorBanner = document.getElementById('error-banner');
|
|
const messageContainer = document.getElementById('message-container');
|
|
|
|
errorBanner.classList.remove('visible');
|
|
messageContainer.classList.remove('visible');
|
|
|
|
if (!encryptedB64) {
|
|
showError('Please paste an encrypted message');
|
|
return;
|
|
}
|
|
|
|
if (!password) {
|
|
showError('Please enter the password');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const message = await BorgSMSG.decrypt(encryptedB64, password);
|
|
displayMessage(message);
|
|
} catch (err) {
|
|
showError('Decryption failed: ' + err.message);
|
|
}
|
|
}
|
|
|
|
// Show error
|
|
function showError(msg) {
|
|
const errorBanner = document.getElementById('error-banner');
|
|
errorBanner.textContent = msg;
|
|
errorBanner.classList.add('visible');
|
|
}
|
|
|
|
// Display decrypted message
|
|
function displayMessage(msg) {
|
|
document.getElementById('msg-from').textContent = msg.from || 'Unknown Sender';
|
|
document.getElementById('msg-subject').textContent = msg.subject || '(No Subject)';
|
|
document.getElementById('msg-body').textContent = msg.body;
|
|
|
|
// Format date
|
|
if (msg.timestamp) {
|
|
const date = new Date(msg.timestamp * 1000);
|
|
document.getElementById('msg-date').textContent = date.toLocaleString();
|
|
} else {
|
|
document.getElementById('msg-date').textContent = '';
|
|
}
|
|
|
|
// Handle attachments
|
|
const attachmentsContainer = document.getElementById('attachments-container');
|
|
const attachmentsList = document.getElementById('attachments-list');
|
|
|
|
// Clear previous attachments
|
|
while (attachmentsList.firstChild) {
|
|
attachmentsList.removeChild(attachmentsList.firstChild);
|
|
}
|
|
attachmentData.clear();
|
|
|
|
if (msg.attachments && msg.attachments.length > 0) {
|
|
attachmentsContainer.style.display = 'block';
|
|
|
|
msg.attachments.forEach((att, index) => {
|
|
// Store attachment data
|
|
const attId = 'att-' + index;
|
|
attachmentData.set(attId, {
|
|
name: att.name,
|
|
content: att.content,
|
|
mime: att.mime
|
|
});
|
|
|
|
const item = document.createElement('div');
|
|
item.className = 'attachment-item';
|
|
|
|
const iconSpan = document.createElement('span');
|
|
iconSpan.className = 'attachment-icon';
|
|
iconSpan.textContent = getFileIcon(att.mime);
|
|
|
|
const infoDiv = document.createElement('div');
|
|
infoDiv.className = 'attachment-info';
|
|
|
|
const nameDiv = document.createElement('div');
|
|
nameDiv.className = 'attachment-name';
|
|
nameDiv.textContent = att.name;
|
|
|
|
const metaDiv = document.createElement('div');
|
|
metaDiv.className = 'attachment-meta';
|
|
metaDiv.textContent = att.mime || 'unknown type';
|
|
|
|
infoDiv.appendChild(nameDiv);
|
|
infoDiv.appendChild(metaDiv);
|
|
|
|
const downloadBtn = document.createElement('button');
|
|
downloadBtn.className = 'secondary attachment-download';
|
|
downloadBtn.textContent = 'Download';
|
|
downloadBtn.dataset.attId = attId;
|
|
downloadBtn.addEventListener('click', function() {
|
|
downloadAttachment(this.dataset.attId);
|
|
});
|
|
|
|
item.appendChild(iconSpan);
|
|
item.appendChild(infoDiv);
|
|
item.appendChild(downloadBtn);
|
|
|
|
attachmentsList.appendChild(item);
|
|
});
|
|
} else {
|
|
attachmentsContainer.style.display = 'none';
|
|
}
|
|
|
|
// Handle reply key
|
|
const replyKeyBanner = document.getElementById('reply-key-banner');
|
|
if (msg.replyKey && msg.replyKey.publicKey) {
|
|
document.getElementById('reply-key').textContent = msg.replyKey.publicKey;
|
|
replyKeyBanner.classList.add('visible');
|
|
} else {
|
|
replyKeyBanner.classList.remove('visible');
|
|
}
|
|
|
|
document.getElementById('message-container').classList.add('visible');
|
|
}
|
|
|
|
// Get file icon based on mime type
|
|
function getFileIcon(mime) {
|
|
if (!mime) return '📄';
|
|
if (mime.startsWith('image/')) return '🖼️';
|
|
if (mime.startsWith('video/')) return '🎬';
|
|
if (mime.startsWith('audio/')) return '🎵';
|
|
if (mime.includes('pdf')) return '📕';
|
|
if (mime.includes('zip') || mime.includes('tar') || mime.includes('gzip')) return '📦';
|
|
if (mime.includes('json') || mime.includes('xml')) return '📋';
|
|
return '📄';
|
|
}
|
|
|
|
// Download attachment
|
|
function downloadAttachment(attId) {
|
|
const att = attachmentData.get(attId);
|
|
if (!att) {
|
|
alert('Attachment not found');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const binary = atob(att.content);
|
|
const bytes = new Uint8Array(binary.length);
|
|
for (let i = 0; i < binary.length; i++) {
|
|
bytes[i] = binary.charCodeAt(i);
|
|
}
|
|
|
|
const blob = new Blob([bytes], { type: att.mime || 'application/octet-stream' });
|
|
const url = URL.createObjectURL(blob);
|
|
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = att.name;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
document.body.removeChild(a);
|
|
URL.revokeObjectURL(url);
|
|
} catch (err) {
|
|
alert('Failed to download: ' + err.message);
|
|
}
|
|
}
|
|
|
|
// Event listeners
|
|
document.getElementById('decrypt-btn').addEventListener('click', decryptMessage);
|
|
document.getElementById('password').addEventListener('keypress', (e) => {
|
|
if (e.key === 'Enter') decryptMessage();
|
|
});
|
|
document.getElementById('encrypted-message').addEventListener('input', checkForHint);
|
|
|
|
// Initialize
|
|
initWasm();
|
|
</script>
|
|
</body>
|
|
</html>
|