v6.8.15: the Android app is out for testing
CodeQL Analysis / Analyze CodeQL (push) Canceled after 0s
Deploy Application / deploy (push) Canceled after 0s
Mirror to Codeberg / mirror (push) Canceled after 0s
Mirror to PrivacyGuides / mirror (push) Canceled after 0s

- Android APK download, served from securebit.chat (/downloads/, never
  committed): a "Download Android APK" badge next to the Snap Store one, drawn
  for both themes, and an Android entry in the download menu (recommended on
  Android devices). nginx serves .apk as an attachment; the service worker
  leaves /downloads/ alone. The APK version is one constant in three places,
  checked by tests/desktop-download-links.test.mjs.
- Roadmap versions follow the real releases: Group Communications v6.0
  shipped, Mobile Edition v6.8 is current, Quantum-Resistant v7.0 is in
  development (2027), then v8.0 and v9.0.
- "Download desktop app" is now "Download the app"; the "You're on Web" pill
  in the download menu is gone.
This commit is contained in:
lockbitchat committed 2026-09-28 01:23:50 -04:00
1 parent 02b007af3e
commit bf5d43b2f1
59 files changed
+663 -529

No files matched your search

Vendored
+7 -7
View File
@@ -1,10 +1,10 @@
function qa(t=globalThis.window){return t?.SECUREBIT_DEBUG===!0}function js({targetWindow:t=globalThis.window,webrtcManagerRef:e,onClearData:n,clearConsole:r=()=>{typeof console.clear=="function"&&console.clear()}}){return qa(t)?(t.forceCleanup=()=>{n(),e.current&&e.current.disconnect()},t.clearLogs=r,t.webrtcManagerRef=e,()=>{delete t.forceCleanup,delete t.clearLogs,delete t.webrtcManagerRef}):()=>{}}var Ya="securebit-net";var es="ice-device-key",ns="ice-settings";function rs(){return typeof indexedDB<"u"&&typeof crypto<"u"&&!!crypto.subtle}function ss(){return new Promise((t,e)=>{let n;try{n=indexedDB.open(Ya,1)}catch(r){e(r);return}n.onupgradeneeded=()=>{let r=n.result;r.objectStoreNames.contains("kv")||r.createObjectStore("kv")},n.onsuccess=()=>t(n.result),n.onerror=()=>e(n.error)})}function ts(t,e){return new Promise((n,r)=>{let c=t.transaction("kv","readonly").objectStore("kv").get(e);c.onsuccess=()=>n(c.result),c.onerror=()=>r(c.error)})}function $s(t,e,n){return new Promise((r,i)=>{let c=t.transaction("kv","readwrite");c.objectStore("kv").put(n,e),c.oncomplete=()=>r(),c.onerror=()=>i(c.error)})}function Ka(t,e){return new Promise((n,r)=>{let i=t.transaction("kv","readwrite");i.objectStore("kv").delete(e),i.oncomplete=()=>n(),i.onerror=()=>r(i.error)})}async function Qa(t){let e=await ts(t,es);if(e instanceof CryptoKey)return e;let n=await crypto.subtle.generateKey({name:"AES-GCM",length:256},!1,["encrypt","decrypt"]);return await $s(t,es,n),n}async function qs(t){if(!rs())throw new Error("Persistent storage is not available in this browser");let e=await ss(),n=await Qa(e),r=JSON.stringify({version:1,servers:Array.isArray(t?.servers)?t.servers:[],privacyMode:t?.privacyMode==="relay-only"?"relay-only":"standard"}),i=crypto.getRandomValues(new Uint8Array(12)),c=await crypto.subtle.encrypt({name:"AES-GCM",iv:i},n,new TextEncoder().encode(r));await $s(e,ns,{iv:Array.from(i),data:Array.from(new Uint8Array(c))})}async function Ys(){if(!rs())return null;try{let t=await ss(),e=await ts(t,ns);if(!e||!Array.isArray(e.iv)||!Array.isArray(e.data))return null;let n=await ts(t,es);if(!(n instanceof CryptoKey))return null;let r=await crypto.subtle.decrypt({name:"AES-GCM",iv:new Uint8Array(e.iv)},n,new Uint8Array(e.data)),i=JSON.parse(new TextDecoder().decode(r));return{servers:Array.isArray(i.servers)?i.servers:[],privacyMode:i.privacyMode==="relay-only"?"relay-only":"standard"}}catch{return null}}async function is(){if(rs())try{let t=await ss();await Ka(t,ns)}catch{}}var Xa="/api/turn-credentials",Xs=["turn.securebit.chat","144.172.96.126"];var Ks=[6e4,3e5,9e5];function Js(t){let e=/^turns?:([^:?\s]+)/i.exec(String(t||"").trim());return e?e[1].toLowerCase():null}function Ja(t){return!t||typeof t!="object"?!1:(Array.isArray(t.urls)?t.urls:[t.urls]).some(n=>Xs.includes(Js(n)))}function er(t){if(typeof t!="string"||t.length===0||t.length>512)return!1;for(let e=0;e<t.length;e++){let n=t.charCodeAt(e);if(n<32||n===127)return!1}return!0}function Za(t,e){if(!Array.isArray(t)||!e||!er(e.username)||!er(e.credential))return!1;let n=!1;for(let r of t)Ja(r)&&(r.username=e.username,r.credential=e.credential,n=!0);if(!n&&Array.isArray(e.urls)){let r=e.urls.filter(i=>er(i)&&Xs.includes(Js(i)));r.length>0&&(t.push({urls:r,username:e.username,credential:e.credential}),n=!0)}return n}function eo(t,e){let n=t&&Array.isArray(t.iceServers)?t.iceServers[0]:null;if(!n||!er(n.username)||!er(n.credential))return null;let r=Number(String(n.username).split(":")[0]);if(!Number.isFinite(r)||r<=e)return null;let i=Number(t.ttl);return{urls:Array.isArray(n.urls)?n.urls.slice(0,8):[],username:n.username,credential:n.credential,expiry:r,ttl:Number.isFinite(i)&&i>0?i:r-e}}var Qs=!1;function Zs(t={}){if(Qs||typeof window>"u"||typeof fetch!="function")return;Qs=!0;let e=t.getList||(()=>window.SECUREBIT_ICE_SERVERS),n=0,r=0,i=null,c=0,d=m=>{i&&clearTimeout(i),i=setTimeout(h,m)};async function h(){i=null;try{let m=await fetch(Xa,{method:"POST",cache:"no-store",credentials:"omit"});if(!m.ok)throw new Error(`HTTP ${m.status}`);let v=eo(await m.json(),Date.now()/1e3);if(!v)throw new Error("unusable response");Za(e(),v),n=v.expiry,r=v.ttl,c=0,d(Math.max(60,r/2)*1e3)}catch{let v=Ks[Math.min(c,Ks.length-1)];c++,d(v)}}document.addEventListener("visibilitychange",()=>{if(document.visibilityState!=="visible")return;let m=Date.now()/1e3;(!n||n-m<r/2)&&h()}),h()}var Gn=["en","de","fr","es","uk","ru","zh","ko","hi","ar","he","fa","ur"],kr={en:{htmlLang:"en",nativeName:"English",abbr:"EN",dir:"ltr",path:"/"},de:{htmlLang:"de",nativeName:"Deutsch",abbr:"DE",dir:"ltr",path:"/de/"},fr:{htmlLang:"fr",nativeName:"Fran\xE7ais",abbr:"FR",dir:"ltr",path:"/fr/"},es:{htmlLang:"es",nativeName:"Espa\xF1ol",abbr:"ES",dir:"ltr",path:"/es/"},uk:{htmlLang:"uk",nativeName:"\u0423\u043A\u0440\u0430\u0457\u043D\u0441\u044C\u043A\u0430",abbr:"UK",dir:"ltr",path:"/uk/"},ru:{htmlLang:"ru",nativeName:"\u0420\u0443\u0441\u0441\u043A\u0438\u0439",abbr:"RU",dir:"ltr",path:"/ru/"},zh:{htmlLine truncated
Please wait for real-time cryptographic verification to complete.`,"sec.verificationUnavailable":"Security verification not available","chat.placeholder":"Type an encrypted message\u2026","chat.send":"Send message","chat.sendFiles":"Send files","chat.hideFiles":"Hide files","chat.recordVoice":"Record voice message","chat.sendVoice":"Send voice message","chat.discard":"Discard","chat.codeBlock":"Send as a code block (expands the input)","chat.codeHint":"Code snippet \xB7 formatting preserved \xB7 \u2318\u21B5 to send","chat.viewOnce":"View once","chat.viewOnceTitle":"View once - vanishes after the peer reads it","chat.viewOncePrefix":"View once \xB7 ","chat.viewOnceTap":"View once \xB7 tap to reveal","chat.viewedOnce":"Viewed once","chat.disappearing":"Disappearing message - deletes on both sides","chat.disappearAfter":"Disappear after","chat.visibleFor":"Visible for","chat.timerPrefix":"Timer \xB7 ","chat.expired":"This message has expired","chat.deleteForEveryone":"Delete for everyone","chat.sending":"Sending","chat.delivered":"Delivered","chat.notSent":"Not sent","chat.notSentReason":"Not sent - the secure channel is not ready. Reconnect to continue.","chat.encrypted":"Encrypted","chat.decrypted":"Decrypted","chat.encryptedOnDevice":"Encrypted on your device","chat.uploading":"Uploading","chat.downloading":"Downloading","chat.transferring":"Transferring\u2026","chat.copied":"Copied!","chat.collapse":"Collapse","chat.closeMenu":"Close menu","chat.newChat":"New chat","chat.newGroup":"New group","chat.createGroup":"Create a group","chat.groupChats":"Group chats","chat.secureChat":"Secure chat","chat.nameThis":"Name this chat","chat.rename":"Rename chat (local only)","chat.renameHint":"Double-click to rename","chat.localLabel":"Local label \xB7 stored only on this device","chat.setStatus":"Set your status","chat.yourStatusPrefix":"Your status - ","chat.meshHint":"Up to 8 peers \xB7 P2P mesh","chat.offline":"Offline","chat.noNetwork":"No network \xB7 reconnecting","chat.startVoiceCall":"Start encrypted voice call","chat.startVideoCall":"Start encrypted video call","chat.verifyForCalls":"Verify the session to enable calls","chat.e2eeNote":"Every message is end-to-end encrypted on your device before it leaves.","chat.peersOnly":"Sent end-to-end to connected peers only - never stored on a server.","flow.invitationCreated":"Secure invitation created","flow.invitationCreatedBang":"Secure invitation created and encrypted!","flow.responseCreated":"Secure response created","flow.responseCreatedBang":"Secure response created!","flow.sendInvitation":"Send the invitation code to your interlocutor via a secure channel (voice call, SMS, etc.).","flow.sendResponse":"Send the response code to the initiator via a secure channel or let them scan the QR code below.","flow.sendEncryptedCode":"Send the encrypted code","flow.sendTheResponse":"Send the response","flow.pasteOrWrite":"Paste or write code\u2026","flow.processingInvitation":"Processing the secure invitation...","flow.processingResponse":"Processing the secure response...","flow.finalizing":"Finalizing the secure connection...","flow.channelEstablished":"Secure channel established","flow.channelReady":"Secure channel is ready","flow.restoring":"Restoring connection\u2026","flow.invitationCaptured":"Invitation captured.","err.needInvitation":"You need to insert the invitation code from your interlocutor.","err.needResponse":"You need to insert the response code from your interlocutor.","err.responseFormat":"Invalid response format - please check the code","err.responseNoKey":"Invalid response code - missing or corrupted cryptographic key. Please check the code and try again.","err.responseNoSignKey":"Invalid response code - missing signature verification key. Please check the code and try again.","err.responseOutdated":"Response data is outdated - please use a fresh invitation","err.setupError":"Connection setup error","err.notReady":"Connection not ready","err.securityBreach":"Security breach detected - connection rejected","err.securityValidation":"Security validation failed - possible attack detected","err.retiredQr":"This QR code uses a retired format that could not transfer the invitation. Ask your peer to generate a new one, or use copy/paste.","err.compressedQrNote":"Compressed QR may omit SDP for brevity. Use copy/paste if connection fails.","err.inviteNotConnected":"The invitation could not be sent. That chat is not connected right now.","err.inviteNotConnectedRetry":"The invitation could not be sent. That chat is not connected right now - reopen it and try again.","err.nobodyAccepted":"Nobody accepted the invitation. The group is unchanged.","err.noAudio":"No audio captured - check microphone permission and try again.","err.voiceNeedsConnection":"Voice message needs an active secure connection. Reconnect and try again.","err.voiceRestoring":"Restoring the connection - try sending the voice message again in a moment.","err.fileTooLarge":"File too large","sas.sa Line truncated
function qa(t=globalThis.window){return t?.SECUREBIT_DEBUG===!0}function js({targetWindow:t=globalThis.window,webrtcManagerRef:e,onClearData:n,clearConsole:r=()=>{typeof console.clear=="function"&&console.clear()}}){return qa(t)?(t.forceCleanup=()=>{n(),e.current&&e.current.disconnect()},t.clearLogs=r,t.webrtcManagerRef=e,()=>{delete t.forceCleanup,delete t.clearLogs,delete t.webrtcManagerRef}):()=>{}}var Ya="securebit-net";var Zr="ice-device-key",ts="ice-settings";function ns(){return typeof indexedDB<"u"&&typeof crypto<"u"&&!!crypto.subtle}function rs(){return new Promise((t,e)=>{let n;try{n=indexedDB.open(Ya,1)}catch(r){e(r);return}n.onupgradeneeded=()=>{let r=n.result;r.objectStoreNames.contains("kv")||r.createObjectStore("kv")},n.onsuccess=()=>t(n.result),n.onerror=()=>e(n.error)})}function es(t,e){return new Promise((n,r)=>{let c=t.transaction("kv","readonly").objectStore("kv").get(e);c.onsuccess=()=>n(c.result),c.onerror=()=>r(c.error)})}function $s(t,e,n){return new Promise((r,i)=>{let c=t.transaction("kv","readwrite");c.objectStore("kv").put(n,e),c.oncomplete=()=>r(),c.onerror=()=>i(c.error)})}function Ka(t,e){return new Promise((n,r)=>{let i=t.transaction("kv","readwrite");i.objectStore("kv").delete(e),i.oncomplete=()=>n(),i.onerror=()=>r(i.error)})}async function Qa(t){let e=await es(t,Zr);if(e instanceof CryptoKey)return e;let n=await crypto.subtle.generateKey({name:"AES-GCM",length:256},!1,["encrypt","decrypt"]);return await $s(t,Zr,n),n}async function qs(t){if(!ns())throw new Error("Persistent storage is not available in this browser");let e=await rs(),n=await Qa(e),r=JSON.stringify({version:1,servers:Array.isArray(t?.servers)?t.servers:[],privacyMode:t?.privacyMode==="relay-only"?"relay-only":"standard"}),i=crypto.getRandomValues(new Uint8Array(12)),c=await crypto.subtle.encrypt({name:"AES-GCM",iv:i},n,new TextEncoder().encode(r));await $s(e,ts,{iv:Array.from(i),data:Array.from(new Uint8Array(c))})}async function Ys(){if(!ns())return null;try{let t=await rs(),e=await es(t,ts);if(!e||!Array.isArray(e.iv)||!Array.isArray(e.data))return null;let n=await es(t,Zr);if(!(n instanceof CryptoKey))return null;let r=await crypto.subtle.decrypt({name:"AES-GCM",iv:new Uint8Array(e.iv)},n,new Uint8Array(e.data)),i=JSON.parse(new TextDecoder().decode(r));return{servers:Array.isArray(i.servers)?i.servers:[],privacyMode:i.privacyMode==="relay-only"?"relay-only":"standard"}}catch{return null}}async function ss(){if(ns())try{let t=await rs();await Ka(t,ts)}catch{}}var Xa="/api/turn-credentials",Xs=["turn.securebit.chat","144.172.96.126"];var Ks=[6e4,3e5,9e5];function Js(t){let e=/^turns?:([^:?\s]+)/i.exec(String(t||"").trim());return e?e[1].toLowerCase():null}function Ja(t){return!t||typeof t!="object"?!1:(Array.isArray(t.urls)?t.urls:[t.urls]).some(n=>Xs.includes(Js(n)))}function tr(t){if(typeof t!="string"||t.length===0||t.length>512)return!1;for(let e=0;e<t.length;e++){let n=t.charCodeAt(e);if(n<32||n===127)return!1}return!0}function Za(t,e){if(!Array.isArray(t)||!e||!tr(e.username)||!tr(e.credential))return!1;let n=!1;for(let r of t)Ja(r)&&(r.username=e.username,r.credential=e.credential,n=!0);if(!n&&Array.isArray(e.urls)){let r=e.urls.filter(i=>tr(i)&&Xs.includes(Js(i)));r.length>0&&(t.push({urls:r,username:e.username,credential:e.credential}),n=!0)}return n}function eo(t,e){let n=t&&Array.isArray(t.iceServers)?t.iceServers[0]:null;if(!n||!tr(n.username)||!tr(n.credential))return null;let r=Number(String(n.username).split(":")[0]);if(!Number.isFinite(r)||r<=e)return null;let i=Number(t.ttl);return{urls:Array.isArray(n.urls)?n.urls.slice(0,8):[],username:n.username,credential:n.credential,expiry:r,ttl:Number.isFinite(i)&&i>0?i:r-e}}var Qs=!1;function Zs(t={}){if(Qs||typeof window>"u"||typeof fetch!="function")return;Qs=!0;let e=t.getList||(()=>window.SECUREBIT_ICE_SERVERS),n=0,r=0,i=null,c=0,d=m=>{i&&clearTimeout(i),i=setTimeout(h,m)};async function h(){i=null;try{let m=await fetch(Xa,{method:"POST",cache:"no-store",credentials:"omit"});if(!m.ok)throw new Error(`HTTP ${m.status}`);let v=eo(await m.json(),Date.now()/1e3);if(!v)throw new Error("unusable response");Za(e(),v),n=v.expiry,r=v.ttl,c=0,d(Math.max(60,r/2)*1e3)}catch{let v=Ks[Math.min(c,Ks.length-1)];c++,d(v)}}document.addEventListener("visibilitychange",()=>{if(document.visibilityState!=="visible")return;let m=Date.now()/1e3;(!n||n-m<r/2)&&h()}),h()}var jn=["en","de","fr","es","uk","ru","zh","ko","hi","ar","he","fa","ur"],wr={en:{htmlLang:"en",nativeName:"English",abbr:"EN",dir:"ltr",path:"/"},de:{htmlLang:"de",nativeName:"Deutsch",abbr:"DE",dir:"ltr",path:"/de/"},fr:{htmlLang:"fr",nativeName:"Fran\xE7ais",abbr:"FR",dir:"ltr",path:"/fr/"},es:{htmlLang:"es",nativeName:"Espa\xF1ol",abbr:"ES",dir:"ltr",path:"/es/"},uk:{htmlLang:"uk",nativeName:"\u0423\u043A\u0440\u0430\u0457\u043D\u0441\u044C\u043A\u0430",abbr:"UK",dir:"ltr",path:"/uk/"},ru:{htmlLang:"ru",nativeName:"\u0420\u0443\u0441\u0441\u043A\u0438\u0439",abbr:"RU",dir:"ltr",path:"/ru/"},zh:{htmlLine truncated
Please wait for real-time cryptographic verification to complete.`,"sec.verificationUnavailable":"Security verification not available","chat.placeholder":"Type an encrypted message\u2026","chat.send":"Send message","chat.sendFiles":"Send files","chat.hideFiles":"Hide files","chat.recordVoice":"Record voice message","chat.sendVoice":"Send voice message","chat.discard":"Discard","chat.codeBlock":"Send as a code block (expands the input)","chat.codeHint":"Code snippet \xB7 formatting preserved \xB7 \u2318\u21B5 to send","chat.viewOnce":"View once","chat.viewOnceTitle":"View once - vanishes after the peer reads it","chat.viewOncePrefix":"View once \xB7 ","chat.viewOnceTap":"View once \xB7 tap to reveal","chat.viewedOnce":"Viewed once","chat.disappearing":"Disappearing message - deletes on both sides","chat.disappearAfter":"Disappear after","chat.visibleFor":"Visible for","chat.timerPrefix":"Timer \xB7 ","chat.expired":"This message has expired","chat.deleteForEveryone":"Delete for everyone","chat.sending":"Sending","chat.delivered":"Delivered","chat.notSent":"Not sent","chat.notSentReason":"Not sent - the secure channel is not ready. Reconnect to continue.","chat.encrypted":"Encrypted","chat.decrypted":"Decrypted","chat.encryptedOnDevice":"Encrypted on your device","chat.uploading":"Uploading","chat.downloading":"Downloading","chat.transferring":"Transferring\u2026","chat.copied":"Copied!","chat.collapse":"Collapse","chat.closeMenu":"Close menu","chat.newChat":"New chat","chat.newGroup":"New group","chat.createGroup":"Create a group","chat.groupChats":"Group chats","chat.secureChat":"Secure chat","chat.nameThis":"Name this chat","chat.rename":"Rename chat (local only)","chat.renameHint":"Double-click to rename","chat.localLabel":"Local label \xB7 stored only on this device","chat.setStatus":"Set your status","chat.yourStatusPrefix":"Your status - ","chat.meshHint":"Up to 8 peers \xB7 P2P mesh","chat.offline":"Offline","chat.noNetwork":"No network \xB7 reconnecting","chat.startVoiceCall":"Start encrypted voice call","chat.startVideoCall":"Start encrypted video call","chat.verifyForCalls":"Verify the session to enable calls","chat.e2eeNote":"Every message is end-to-end encrypted on your device before it leaves.","chat.peersOnly":"Sent end-to-end to connected peers only - never stored on a server.","flow.invitationCreated":"Secure invitation created","flow.invitationCreatedBang":"Secure invitation created and encrypted!","flow.responseCreated":"Secure response created","flow.responseCreatedBang":"Secure response created!","flow.sendInvitation":"Send the invitation code to your interlocutor via a secure channel (voice call, SMS, etc.).","flow.sendResponse":"Send the response code to the initiator via a secure channel or let them scan the QR code below.","flow.sendEncryptedCode":"Send the encrypted code","flow.sendTheResponse":"Send the response","flow.pasteOrWrite":"Paste or write code\u2026","flow.processingInvitation":"Processing the secure invitation...","flow.processingResponse":"Processing the secure response...","flow.finalizing":"Finalizing the secure connection...","flow.channelEstablished":"Secure channel established","flow.channelReady":"Secure channel is ready","flow.restoring":"Restoring connection\u2026","flow.invitationCaptured":"Invitation captured.","err.needInvitation":"You need to insert the invitation code from your interlocutor.","err.needResponse":"You need to insert the response code from your interlocutor.","err.responseFormat":"Invalid response format - please check the code","err.responseNoKey":"Invalid response code - missing or corrupted cryptographic key. Please check the code and try again.","err.responseNoSignKey":"Invalid response code - missing signature verification key. Please check the code and try again.","err.responseOutdated":"Response data is outdated - please use a fresh invitation","err.setupError":"Connection setup error","err.notReady":"Connection not ready","err.securityBreach":"Security breach detected - connection rejected","err.securityValidation":"Security validation failed - possible attack detected","err.retiredQr":"This QR code uses a retired format that could not transfer the invitation. Ask your peer to generate a new one, or use copy/paste.","err.compressedQrNote":"Compressed QR may omit SDP for brevity. Use copy/paste if connection fails.","err.inviteNotConnected":"The invitation could not be sent. That chat is not connected right now.","err.inviteNotConnectedRetry":"The invitation could not be sent. That chat is not connected right now - reopen it and try again.","err.nobodyAccepted":"Nobody accepted the invitation. The group is unchanged.","err.noAudio":"No audio captured - check microphone permission and try again.","err.voiceNeedsConnection":"Voice message needs an active secure connection. Reconnect and try again.","err.voiceRestoring":"Restoring the connection - try sending the voice message again in a moment.","err.fileTooLarge":"File too large","sas.sa Line truncated
`),n.style.transform="translate3d(0,"+(t?Math.round(t.offsetTop):0)+"px,0)"};i();let c=setInterval(i,200);return()=>{clearInterval(c),n.remove()}}var tc=t=>()=>{if(t&&t.current){let e=()=>{t.current&&t.current.scrollTo({top:t.current.scrollHeight,behavior:Or()})};e(),setTimeout(e,50),setTimeout(e,150),setTimeout(e,300),requestAnimationFrame(()=>{setTimeout(e,100)})}},ia=async t=>{let e=null;try{t&&window.EnhancedSecureCryptoUtils&&(e=await window.EnhancedSecureCryptoUtils.calculateSecurityLevel(t))}catch{}if(!e){alert(`Security verification in progress\u2026
Please wait for real-time cryptographic verification to complete.`);return}let n="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace",r=z=>String(z).replace(/[&<>"]/g,Q=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"})[Q]),i=e.color==="orange"?"var(--sb-orange-solid)":e.color==="yellow"?"var(--sb-yellow-solid)":e.color==="red"?"var(--sb-red-solid)":"var(--sb-green-solid)",c=e.color==="orange"?"var(--sb-orange)":e.color==="yellow"?"var(--sb-yellow)":e.color==="red"?"var(--sb-red)":"var(--sb-green)",d=e.color==="orange"?"var(--sb-orange-rgb)":e.color==="yellow"?"var(--sb-yellow-rgb)":e.color==="red"?"var(--sb-red-rgb)":"var(--sb-green-rgb)",h=Math.max(0,Math.min(100,Math.round(e.score||0))),m=2*Math.PI*56,v=`${(m*Math.min(1,h/100)).toFixed(1)} ${m.toFixed(1)}`,S=String(e.level||"SECURE").toUpperCase(),p=e.isRealData!==!1,k=e.verificationResults?Object.entries(e.verificationResults):[],y=Number.isFinite(e.passedChecks)?e.passedChecks:k.filter(([,z])=>z&&z.passed).length,I=Number.isFinite(e.totalChecks)?e.totalChecks:k.length,B=new Date(e.timestamp||Date.now()).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:!1}),D=z=>{let Q=a(`secTest.${z}`);return Q===`secTest.${z}`?le(z):Q},le=z=>{let Q=String(z).replace(/^verify/i,"").replace(/([a-z0-9])([A-Z])/g,"$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g,"$1 $2").trim();return Q=Q.replace(/\b(ecdh|ecdsa|aes|gcm|hmac|pfs|sas|mitm|asn|dtls|hkdf|spki|oid|p384)\b/gi,X=>X.toUpperCase()),Q.charAt(0).toUpperCase()+Q.slice(1)},ee='<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="var(--sb-green-solid)" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M5 13l4 4 10-11"/></svg>',ge='<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="var(--sb-red-solid)" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6L6 18M6 6l12 12"/></svg>',we=k.map(([z,Q],X)=>{let W=!!(Q&&Q.passed),se=Q&&Q.details||(W?a("sec.testPassed"):a("sec.testFailed")),ce=a(`secDetail.${se}`),me=ce===`secDetail.${se}`?se:ce,L=W?"var(--sb-surface)":"var(--sb-bg)",j=W?"rgba(var(--sb-green-rgb), 0.16)":"rgba(var(--sb-red-rgb), 0.18)",ne=W?"rgba(var(--sb-green-rgb), 0.12)":"rgba(var(--sb-red-rgb), 0.1)",Ae=W?"rgba(var(--sb-green-rgb), 0.26)":"rgba(var(--sb-red-rgb), 0.24)",$=W?"var(--sb-text-1)":"var(--sb-text-4)";return`<div style="display:flex; align-items:flex-start; gap:13px; padding:16px 18px; border-radius:14px; background:${L}; border:1px solid ${j}; animation:svIn .3s cubic-bezier(.2,.7,.3,1) both; animation-delay:${(X*.04).toFixed(2)}s;">
<span style="flex:none; width:34px; height:34px; border-radius:9px; display:grid; place-items:center; background:${ne}; border:1px solid ${Ae};">${W?ee:ge}</span>
Please wait for real-time cryptographic verification to complete.`);return}let n="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace",r=U=>String(U).replace(/[&<>"]/g,Q=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"})[Q]),i=e.color==="orange"?"var(--sb-orange-solid)":e.color==="yellow"?"var(--sb-yellow-solid)":e.color==="red"?"var(--sb-red-solid)":"var(--sb-green-solid)",c=e.color==="orange"?"var(--sb-orange)":e.color==="yellow"?"var(--sb-yellow)":e.color==="red"?"var(--sb-red)":"var(--sb-green)",d=e.color==="orange"?"var(--sb-orange-rgb)":e.color==="yellow"?"var(--sb-yellow-rgb)":e.color==="red"?"var(--sb-red-rgb)":"var(--sb-green-rgb)",h=Math.max(0,Math.min(100,Math.round(e.score||0))),m=2*Math.PI*56,v=`${(m*Math.min(1,h/100)).toFixed(1)} ${m.toFixed(1)}`,S=String(e.level||"SECURE").toUpperCase(),p=e.isRealData!==!1,k=e.verificationResults?Object.entries(e.verificationResults):[],y=Number.isFinite(e.passedChecks)?e.passedChecks:k.filter(([,U])=>U&&U.passed).length,I=Number.isFinite(e.totalChecks)?e.totalChecks:k.length,z=new Date(e.timestamp||Date.now()).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:!1}),N=U=>{let Q=a(`secTest.${U}`);return Q===`secTest.${U}`?ce(U):Q},ce=U=>{let Q=String(U).replace(/^verify/i,"").replace(/([a-z0-9])([A-Z])/g,"$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g,"$1 $2").trim();return Q=Q.replace(/\b(ecdh|ecdsa|aes|gcm|hmac|pfs|sas|mitm|asn|dtls|hkdf|spki|oid|p384)\b/gi,X=>X.toUpperCase()),Q.charAt(0).toUpperCase()+Q.slice(1)},ee='<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="var(--sb-green-solid)" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M5 13l4 4 10-11"/></svg>',ge='<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="var(--sb-red-solid)" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6L6 18M6 6l12 12"/></svg>',we=k.map(([U,Q],X)=>{let V=!!(Q&&Q.passed),re=Q&&Q.details||(V?a("sec.testPassed"):a("sec.testFailed")),oe=a(`secDetail.${re}`),me=oe===`secDetail.${re}`?re:oe,B=V?"var(--sb-surface)":"var(--sb-bg)",$=V?"rgba(var(--sb-green-rgb), 0.16)":"rgba(var(--sb-red-rgb), 0.18)",ne=V?"rgba(var(--sb-green-rgb), 0.12)":"rgba(var(--sb-red-rgb), 0.1)",Ie=V?"rgba(var(--sb-green-rgb), 0.26)":"rgba(var(--sb-red-rgb), 0.24)",q=V?"var(--sb-text-1)":"var(--sb-text-4)";return`<div style="display:flex; align-items:flex-start; gap:13px; padding:16px 18px; border-radius:14px; background:${B}; border:1px solid ${$}; animation:svIn .3s cubic-bezier(.2,.7,.3,1) both; animation-delay:${(X*.04).toFixed(2)}s;">
<span style="flex:none; width:34px; height:34px; border-radius:9px; display:grid; place-items:center; background:${ne}; border:1px solid ${Ie};">${V?ee:ge}</span>
<div style="flex:1; min-width:0;">
<div style="font-size:14.5px; font-weight:700; letter-spacing:-0.2px; color:${$}; margin-bottom:3px;">${r(D(z))}</div>
<div style="font-size:14.5px; font-weight:700; letter-spacing:-0.2px; color:${q}; margin-bottom:3px;">${r(N(U))}</div>
<div style="font-family:${n}; font-size:11.5px; line-height:1.45; color:var(--sb-text-7);">${r(me)}</div>
</div>
</div>`}).join(""),C=document.createElement("div");C.id="sb-security-report",C.style.cssText="position:fixed; inset:0; z-index:10000; display:flex; align-items:center; justify-content:center; padding:24px; background:rgba(var(--sb-scrim-rgb), 0.62); backdrop-filter:blur(4px); -webkit-backdrop-filter:blur(4px); font-family:'Manrope',system-ui,-apple-system,sans-serif; overflow:auto;",C.innerHTML=`
@@ -33,7 +33,7 @@ Please wait for real-time cryptographic verification to complete.`);return}let n
</div>
<div style="display:flex; gap:28px; flex-wrap:wrap;">
<div><div style="font-family:${n}; font-size:10px; font-weight:600; color:var(--sb-text-faint); text-transform:uppercase; letter-spacing:1px; margin-bottom:4px;">${a("report.testsPassed")}</div><div style="font-size:15px; font-weight:700; color:var(--sb-text-2);"><span style="color:${c};">${y}</span> / ${I}</div></div>
<div><div style="font-family:${n}; font-size:10px; font-weight:600; color:var(--sb-text-faint); text-transform:uppercase; letter-spacing:1px; margin-bottom:4px;">${a("report.verifiedAt")}</div><div style="font-family:${n}; font-size:15px; font-weight:600; color:var(--sb-text-2);">${r(B)}</div></div>
<div><div style="font-family:${n}; font-size:10px; font-weight:600; color:var(--sb-text-faint); text-transform:uppercase; letter-spacing:1px; margin-bottom:4px;">${a("report.verifiedAt")}</div><div style="font-family:${n}; font-size:15px; font-weight:600; color:var(--sb-text-2);">${r(z)}</div></div>
<div><div style="font-family:${n}; font-size:10px; font-weight:600; color:var(--sb-text-faint); text-transform:uppercase; letter-spacing:1px; margin-bottom:4px;">${a("report.source")}</div><div style="font-size:15px; font-weight:600; color:var(--sb-text-2);">${p?a("sec.realTests"):a("sec.simulatedData")}</div></div>
</div>
</div>
@@ -47,5 +47,5 @@ Please wait for real-time cryptographic verification to complete.`);return}let n
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="${i}" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" style="flex:none;"><path d="M12 3l8 4v5c0 4.5-3.2 7.8-8 9-4.8-1.2-8-4.5-8-9V7l8-4z"/><path d="M9.2 12.2l2 2 3.6-3.8"/></svg>
<span style="font-size:14px; font-weight:600; color:var(--sb-text-2);">${p?a("sec.realTimeNote"):a("sec.simulatedWarning")}</span>
</div>
</div>`;let P=z=>{z.key==="Escape"&&V()},V=()=>{C.parentNode&&C.remove(),document.removeEventListener("keydown",P)};C.querySelector(".sv-close").addEventListener("click",V),C.addEventListener("click",z=>{z.target===C&&V()}),document.addEventListener("keydown",P);let O=C.querySelector(".sv-rerun");O.addEventListener("mouseenter",()=>{O.style.borderColor="rgba(var(--sb-orange-rgb), 0.45)",O.style.color="var(--sb-orange)"}),O.addEventListener("mouseleave",()=>{O.style.borderColor="rgba(var(--sb-ink), 0.1)",O.style.color="var(--sb-text-4)"}),O.addEventListener("click",()=>{V(),ia(t)}),document.body.appendChild(C)},nc=({status:t,onDisconnect:e,webrtcManager:n,title:r,isOffline:i,peerPresence:c,onRenameTitle:d})=>{let h="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace",[m,v]=React.useState(!1),[S,p]=React.useState(null),[k,y]=React.useState(!1),[I,B]=React.useState("");React.useEffect(()=>{let $=!0,xe=async()=>{try{if(!n)return;let Ee=null;typeof n.getRealSecurityLevel=="function"?Ee=await n.getRealSecurityLevel():typeof n.calculateAndReportSecurityLevel=="function"?Ee=await n.calculateAndReportSecurityLevel():window.EnhancedSecureCryptoUtils&&(Ee=await window.EnhancedSecureCryptoUtils.calculateSecurityLevel(n)),$&&Ee&&Ee.isRealData!==!1&&p(Ee)}catch{}};xe();let Le=Ee=>{$&&Ee.detail&&Ee.detail.securityData&&p(Ee.detail.securityData)};document.addEventListener("real-security-calculated",Le);let at=setInterval(xe,15e3);return()=>{$=!1,clearInterval(at),document.removeEventListener("real-security-calculated",Le)}},[n]);let D=t==="connected"||t==="verified",le=t==="disconnected"||t==="peer_disconnected",ee=D&&!i,ge=i||le?"var(--sb-red-solid)":D?"var(--sb-green-solid)":"var(--sb-yellow-solid)",we=i?a("chat.offline"):D?a("status.connected"):t==="peer_disconnected"?a("status.peerDisconnected"):t==="disconnected"?a("chat.disconnected"):a("status.connecting"),C=i||le?"0 0 0 3px rgba(var(--sb-red-rgb), 0.16)":D?"0 0 0 3px rgba(var(--sb-green-rgb), 0.16)":"0 0 0 3px rgba(var(--sb-yellow-rgb), 0.16)",P=D&&!i?ls[c]||"var(--sb-green-solid)":ge,V=D&&!i&&c&&ds[c]||null,O=()=>{B(r||""),y(!0)},z=()=>{typeof d=="function"&&d(I),y(!1)},Q=$=>{$.key==="Enter"?($.preventDefault(),z()):$.key==="Escape"&&y(!1)},X=S&&Number.isFinite(S.passedChecks)?S.passedChecks:null,W=S&&Number.isFinite(S.totalChecks)?S.totalChecks:null,se=$=>{if(!$)return $;let xe=a(`secLevel.${$}`);return xe===`secLevel.${$}`?$:xe},ce=X!=null&&W?X+"/"+W:S?S.score+"%":"\u2014",me=S?S.color==="green"?"var(--sb-green)":S.color==="orange"?"var(--sb-orange)":S.color==="yellow"?"var(--sb-yellow)":"var(--sb-red)":"var(--sb-green)",L=React.createElement("div",{key:"sec",title:a("sas.runVerification"),onClick:()=>ia(n),className:"sb-secpill",style:{display:"flex",alignItems:"center",gap:"9px",padding:"7px 13px",borderRadius:"9px",border:"1px solid "+(m?"rgba(var(--sb-ink), 0.16)":"rgba(var(--sb-ink), 0.07)"),background:m?"rgba(var(--sb-ink), 0.05)":"rgba(var(--sb-ink), 0.02)",cursor:"pointer",fontFamily:"inherit",transition:"all .15s"}},[React.createElement("i",{key:"i",className:"fas fa-shield-halved",style:{color:me,fontSize:"13px"}}),React.createElement("span",{key:"l",className:"sb-sec-label",style:{fontSize:"13px",fontWeight:600,color:"var(--sb-text-2)"}},S?se(S.level)||a("chatHdr.secure"):a("chatHdr.secure")),React.createElement("span",{key:"d",className:"sb-sec-div",style:{width:"1px",height:"13px",background:"rgba(var(--sb-ink), 0.12)"}}),React.createElement("span",{key:"s",className:"sb-sec-score",style:{fontFamily:h,fontSize:"11.5px",fontWeight:500,color:"var(--sb-text-7)"}},ce),React.createElement("button",{key:"c",type:"button",title:a("sec.panelTitle"),onClick:$=>{$.stopPropagation(),v(xe=>!xe)},style:{background:"none",border:"none",padding:0,margin:0,cursor:"pointer",display:"grid",placeItems:"center"}},React.createElement("i",{className:"fas fa-chevron-down",style:{color:"var(--sb-text-9)",fontSize:"11px",transform:m?"rotate(180deg)":"rotate(0deg)",transition:"transform .2s"}}))]),j=React.createElement("style",{key:"hdr-css",dangerouslySetInnerHTML:{__html:".sb-call-btn:not(.sb-call-off):hover{border-color:rgba(var(--sb-green-rgb), 0.45) !important;color:var(--sb-green) !important;background:rgba(var(--sb-green-rgb), 0.07) !important;}@media (max-width:768px){.sb-chat-header{padding-inline-start:58px !important;gap:8px !important;}.sb-chat-header .sb-secpill{display:none !important;}.sb-chat-header .sb-conn-text{display:none !important;}.sb-chat-header .sb-conn{padding:9px !important;}.sb-chat-header .sb-hdr-sub{display:none !important;}.sb-chat-header .sb-disconnect{width:40px !important;height:40px !important;padding:0 !important;gap:0 !important;justify-content:center !important;border-radius:9px !important;color:var(--sb-red) !important;border-color:rgba(var(--sb-red-rgb), 0.28) !important;}.sb-chat-header .sb-disconnect .sb-hide-sm{display:none !important;}}@media (max-width:600px){.sb-chips{flex-wrap:nowrap !important;jLine truncated
</div>`;let O=U=>{U.key==="Escape"&&j()},j=()=>{C.parentNode&&C.remove(),document.removeEventListener("keydown",O)};C.querySelector(".sv-close").addEventListener("click",j),C.addEventListener("click",U=>{U.target===C&&j()}),document.addEventListener("keydown",O);let P=C.querySelector(".sv-rerun");P.addEventListener("mouseenter",()=>{P.style.borderColor="rgba(var(--sb-orange-rgb), 0.45)",P.style.color="var(--sb-orange)"}),P.addEventListener("mouseleave",()=>{P.style.borderColor="rgba(var(--sb-ink), 0.1)",P.style.color="var(--sb-text-4)"}),P.addEventListener("click",()=>{j(),ia(t)}),document.body.appendChild(C)},nc=({status:t,onDisconnect:e,webrtcManager:n,title:r,isOffline:i,peerPresence:c,onRenameTitle:d})=>{let h="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace",[m,v]=React.useState(!1),[S,p]=React.useState(null),[k,y]=React.useState(!1),[I,z]=React.useState("");React.useEffect(()=>{let q=!0,xe=async()=>{try{if(!n)return;let Ee=null;typeof n.getRealSecurityLevel=="function"?Ee=await n.getRealSecurityLevel():typeof n.calculateAndReportSecurityLevel=="function"?Ee=await n.calculateAndReportSecurityLevel():window.EnhancedSecureCryptoUtils&&(Ee=await window.EnhancedSecureCryptoUtils.calculateSecurityLevel(n)),q&&Ee&&Ee.isRealData!==!1&&p(Ee)}catch{}};xe();let Le=Ee=>{q&&Ee.detail&&Ee.detail.securityData&&p(Ee.detail.securityData)};document.addEventListener("real-security-calculated",Le);let at=setInterval(xe,15e3);return()=>{q=!1,clearInterval(at),document.removeEventListener("real-security-calculated",Le)}},[n]);let N=t==="connected"||t==="verified",ce=t==="disconnected"||t==="peer_disconnected",ee=N&&!i,ge=i||ce?"var(--sb-red-solid)":N?"var(--sb-green-solid)":"var(--sb-yellow-solid)",we=i?a("chat.offline"):N?a("status.connected"):t==="peer_disconnected"?a("status.peerDisconnected"):t==="disconnected"?a("chat.disconnected"):a("status.connecting"),C=i||ce?"0 0 0 3px rgba(var(--sb-red-rgb), 0.16)":N?"0 0 0 3px rgba(var(--sb-green-rgb), 0.16)":"0 0 0 3px rgba(var(--sb-yellow-rgb), 0.16)",O=N&&!i?cs[c]||"var(--sb-green-solid)":ge,j=N&&!i&&c&&ls[c]||null,P=()=>{z(r||""),y(!0)},U=()=>{typeof d=="function"&&d(I),y(!1)},Q=q=>{q.key==="Enter"?(q.preventDefault(),U()):q.key==="Escape"&&y(!1)},X=S&&Number.isFinite(S.passedChecks)?S.passedChecks:null,V=S&&Number.isFinite(S.totalChecks)?S.totalChecks:null,re=q=>{if(!q)return q;let xe=a(`secLevel.${q}`);return xe===`secLevel.${q}`?q:xe},oe=X!=null&&V?X+"/"+V:S?S.score+"%":"\u2014",me=S?S.color==="green"?"var(--sb-green)":S.color==="orange"?"var(--sb-orange)":S.color==="yellow"?"var(--sb-yellow)":"var(--sb-red)":"var(--sb-green)",B=React.createElement("div",{key:"sec",title:a("sas.runVerification"),onClick:()=>ia(n),className:"sb-secpill",style:{display:"flex",alignItems:"center",gap:"9px",padding:"7px 13px",borderRadius:"9px",border:"1px solid "+(m?"rgba(var(--sb-ink), 0.16)":"rgba(var(--sb-ink), 0.07)"),background:m?"rgba(var(--sb-ink), 0.05)":"rgba(var(--sb-ink), 0.02)",cursor:"pointer",fontFamily:"inherit",transition:"all .15s"}},[React.createElement("i",{key:"i",className:"fas fa-shield-halved",style:{color:me,fontSize:"13px"}}),React.createElement("span",{key:"l",className:"sb-sec-label",style:{fontSize:"13px",fontWeight:600,color:"var(--sb-text-2)"}},S?re(S.level)||a("chatHdr.secure"):a("chatHdr.secure")),React.createElement("span",{key:"d",className:"sb-sec-div",style:{width:"1px",height:"13px",background:"rgba(var(--sb-ink), 0.12)"}}),React.createElement("span",{key:"s",className:"sb-sec-score",style:{fontFamily:h,fontSize:"11.5px",fontWeight:500,color:"var(--sb-text-7)"}},oe),React.createElement("button",{key:"c",type:"button",title:a("sec.panelTitle"),onClick:q=>{q.stopPropagation(),v(xe=>!xe)},style:{background:"none",border:"none",padding:0,margin:0,cursor:"pointer",display:"grid",placeItems:"center"}},React.createElement("i",{className:"fas fa-chevron-down",style:{color:"var(--sb-text-9)",fontSize:"11px",transform:m?"rotate(180deg)":"rotate(0deg)",transition:"transform .2s"}}))]),$=React.createElement("style",{key:"hdr-css",dangerouslySetInnerHTML:{__html:".sb-call-btn:not(.sb-call-off):hover{border-color:rgba(var(--sb-green-rgb), 0.45) !important;color:var(--sb-green) !important;background:rgba(var(--sb-green-rgb), 0.07) !important;}@media (max-width:768px){.sb-chat-header{padding-inline-start:58px !important;gap:8px !important;}.sb-chat-header .sb-secpill{display:none !important;}.sb-chat-header .sb-conn-text{display:none !important;}.sb-chat-header .sb-conn{padding:9px !important;}.sb-chat-header .sb-hdr-sub{display:none !important;}.sb-chat-header .sb-disconnect{width:40px !important;height:40px !important;padding:0 !important;gap:0 !important;justify-content:center !important;border-radius:9px !important;color:var(--sb-red) !important;border-color:rgba(var(--sb-red-rgb), 0.28) !important;}.sb-chat-header .sb-disconnect .sb-hide-sm{display:none !important;}}@media (max-width:600px){.sb-chips{flex-wrap:nowrap !important;jLine truncated
//# sourceMappingURL=app.js.map