release: v4.9.0 — full redesign + reworked offline mode
CodeQL Analysis / Analyze CodeQL (push) Has been cancelled
Deploy Application / deploy (push) Has been cancelled
Mirror to Codeberg / mirror (push) Has been cancelled
Mirror to PrivacyGuides / mirror (push) Has been cancelled

Ground-up visual redesign across the entire surface (landing, connection
setup, chat header, security verification report, file transfer, PWA
install/update/offline dialogs).

Offline reworked: store-and-forward queue (send while offline → queued,
delivered on reconnect), WhatsApp-style per-message delivery status
(sending/sent/delivered/not-sent) via delivery receipts, offline buffering
for messages to an offline peer, and offline state no longer leaking into
the connection indicator. Resilient chunked file transfer with retransmission
and auto-save. README + screenshots added.
This commit is contained in:
lockbitchat
2026-06-23 16:52:30 -04:00
parent b39f9ecd2c
commit cbf5c5f834
50 changed files with 5573 additions and 5308 deletions
+1 -3
View File
@@ -8,10 +8,8 @@ import '../components/ui/Header.jsx';
import '../components/ui/DownloadApps.jsx';
import '../components/ui/BecomePartner.jsx';
import '../components/ui/UniqueFeatureSlider.jsx';
import '../components/ui/SecurityFeatures.jsx';
import '../components/ui/Testimonials.jsx';
import '../components/ui/ComparisonTable.jsx';
import '../components/ui/Roadmap.jsx';
import '../components/ui/CommunityCTA.jsx';
import '../components/ui/FileTransfer.jsx';
import '../components/ui/IceServerSettings.jsx';
+86 -29
View File
@@ -24,41 +24,98 @@ if (window.DEBUG_MODE) {
console.log('✅ Global timer management functions loaded');
}
// Inline onclick replacement for update notification button
function attachUpdateNotificationHandlers(container) {
const btn = container.querySelector('[data-action="reload"]');
if (btn) {
btn.addEventListener('click', () => window.location.reload());
}
const dismissBtn = container.querySelector('[data-action="dismiss-notification"]');
if (dismissBtn) {
dismissBtn.addEventListener('click', () => {
const host = dismissBtn.closest('div');
if (host && host.parentElement) host.parentElement.remove();
// Format a version (build timestamp -> date, or pass through a semver string)
function formatUpdateVersion(v) {
if (!v) return null;
if (/^\d+$/.test(String(v))) {
return new Date(parseInt(v, 10)).toLocaleString('en-US', {
year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit'
});
}
return String(v);
}
// Update notification — translated from the Claude Design component
// (Update Notification.dc.html). Centered modal with version comparison.
window.showUpdateNotification = function showUpdateNotification() {
if (window.DEBUG_MODE) console.log('🆕 Showing update notification for PWA');
const notification = document.createElement('div');
notification.className = 'fixed top-4 left-1/2 transform -translate-x-1/2 bg-blue-500 text-white p-4 rounded-lg shadow-lg z-50 max-w-sm';
notification.innerHTML = `
<div class="flex items-center space-x-3">
<i class="fas fa-download text-lg"></i>
<div class="flex-1">
<div class="font-medium">Update Available</div>
<div class="text-sm opacity-90">SecureBit.chat v4.4.18 - ECDH + DTLS + SAS is ready</div>
</div>
<button data-action="reload" class="bg-white/20 hover:bg-white/30 px-3 py-1 rounded text-sm font-medium transition-colors">
Update
</button>
</div>`;
document.body.appendChild(notification);
attachUpdateNotificationHandlers(notification);
setTimeout(() => {
if (notification.parentElement) notification.remove();
}, 30000);
// Avoid stacking duplicates if the SW fires more than once.
const existing = document.getElementById('pwa-update-modal');
if (existing) existing.remove();
if (!document.getElementById('pwa-update-modal-kf')) {
const style = document.createElement('style');
style.id = 'pwa-update-modal-kf';
style.textContent =
'@keyframes unPop{from{opacity:0;transform:scale(.96) translateY(10px)}to{opacity:1;transform:scale(1) translateY(0)}}' +
'@keyframes unFade{from{opacity:0}to{opacity:1}}' +
'@keyframes unSpin{to{transform:rotate(360deg)}}';
document.head.appendChild(style);
}
let currentVersion = null;
try { currentVersion = localStorage.getItem('app_version'); } catch (e) {}
const currentStr = formatUpdateVersion(currentVersion) || 'Installed build';
const modal = document.createElement('div');
modal.id = 'pwa-update-modal';
modal.style.cssText = "position:fixed; inset:0; z-index:9999; display:flex; align-items:center; justify-content:center; padding:24px; background:rgba(8,8,10,0.55); backdrop-filter:blur(3px); -webkit-backdrop-filter:blur(3px); animation:unFade .3s ease; font-family:'Manrope',system-ui,-apple-system,sans-serif;";
modal.innerHTML = `
<div style="position:relative; width:440px; max-width:calc(100vw - 48px); border-radius:22px; background:#121214; border:1px solid rgba(255,255,255,0.08); padding:36px 32px 28px; text-align:center; box-shadow:0 30px 70px rgba(0,0,0,0.6); animation:unPop .32s cubic-bezier(.2,.7,.3,1);">
<div style="display:inline-flex; width:64px; height:64px; border-radius:50%; align-items:center; justify-content:center; background:rgba(240,137,42,0.12); border:1px solid rgba(240,137,42,0.3); margin-bottom:20px;">
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="#f0892a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="animation:unSpin 6s linear infinite;"><path d="M21 8a8.5 8.5 0 0 0-15.6-2.5M3 4v4h4"/><path d="M3 16a8.5 8.5 0 0 0 15.6 2.5M21 20v-4h-4"/></svg>
</div>
<h2 style="margin:0 0 9px; font-size:26px; font-weight:800; letter-spacing:-0.7px; color:#f4f4f6;">Update available</h2>
<p style="margin:0 0 24px; font-size:14.5px; line-height:1.55; color:#9a9aa2;">A newer version of SecureBit has been detected.</p>
<div style="border-radius:14px; background:#0c0c0e; border:1px solid rgba(255,255,255,0.06); padding:16px 18px; margin-bottom:24px; text-align:left;">
<div style="display:flex; align-items:center; justify-content:space-between; gap:14px; padding:5px 0;">
<span style="font-size:13.5px; font-weight:500; color:#8a8a92;">Current version</span>
<span class="cur-ver" style="font-family:'JetBrains Mono',ui-monospace,Menlo,monospace; font-size:13px; font-weight:500; color:#9a9aa2; white-space:nowrap;">${currentStr}</span>
</div>
<div style="height:1px; background:rgba(255,255,255,0.05); margin:4px 0;"></div>
<div style="display:flex; align-items:center; justify-content:space-between; gap:14px; padding:5px 0;">
<span style="display:inline-flex; align-items:center; gap:8px; font-size:13.5px; font-weight:600; color:#e8e8eb;"><span style="width:6px; height:6px; border-radius:50%; background:#f0892a;"></span>New version</span>
<span class="new-ver" style="font-family:'JetBrains Mono',ui-monospace,Menlo,monospace; font-size:13px; font-weight:700; color:#f0892a; white-space:nowrap;">Latest</span>
</div>
</div>
<div style="display:flex; align-items:center; gap:12px;">
<button class="upd-now" type="button" style="flex:1; display:inline-flex; align-items:center; justify-content:center; gap:10px; padding:15px 20px; border-radius:13px; border:none; background:#f0892a; color:#1a0f04; font-family:inherit; font-size:15.5px; font-weight:700; letter-spacing:-0.2px; cursor:pointer; box-shadow:0 8px 24px rgba(240,137,42,0.28); transition:all .2s cubic-bezier(.2,.7,.3,1);">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round" style="pointer-events:none;"><path d="M12 3v11"/><path d="M7.5 10.5L12 15l4.5-4.5"/><path d="M5 20h14"/></svg>
Update now
</button>
<button class="upd-later" type="button" title="Later" style="flex:none; width:50px; height:50px; border-radius:13px; display:grid; place-items:center; border:1px solid rgba(255,255,255,0.1); background:rgba(255,255,255,0.025); color:#9a9aa2; cursor:pointer; transition:all .18s cubic-bezier(.2,.7,.3,1);">
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round" style="pointer-events:none;"><path d="M6 6l12 12M18 6L6 18"/></svg>
</button>
</div>
</div>`;
const updNow = modal.querySelector('.upd-now');
updNow.addEventListener('mouseenter', () => { updNow.style.background = '#ff9637'; updNow.style.transform = 'translateY(-2px)'; });
updNow.addEventListener('mouseleave', () => { updNow.style.background = '#f0892a'; updNow.style.transform = 'none'; });
updNow.addEventListener('click', () => window.location.reload());
const updLater = modal.querySelector('.upd-later');
updLater.addEventListener('mouseenter', () => { updLater.style.color = '#e5727a'; updLater.style.borderColor = 'rgba(229,114,122,0.4)'; });
updLater.addEventListener('mouseleave', () => { updLater.style.color = '#9a9aa2'; updLater.style.borderColor = 'rgba(255,255,255,0.1)'; });
updLater.addEventListener('click', () => modal.remove());
document.body.appendChild(modal);
// Fill in the new version once meta.json is fetched (best-effort).
fetch('/meta.json?t=' + Date.now(), { cache: 'no-store' })
.then((r) => r.json())
.then((meta) => {
const label = meta.appVersion
? ('v' + meta.appVersion)
: (formatUpdateVersion(meta.version || meta.buildVersion) || 'Latest');
const el = modal.querySelector('.new-ver');
if (el) el.textContent = label;
})
.catch(() => {});
};
window.showServiceWorkerError = function showServiceWorkerError(error) {