' : ''; }
function saveSession() { if (state.session) localStorage.setItem(sessionKey(), JSON.stringify(state.session)); else localStorage.removeItem(sessionKey()); }
function loadSession() { try { var r = localStorage.getItem(sessionKey()); if (!r) return null; var p = JSON.parse(r); if (!p || !p.accessToken || !p.refreshToken) return null; return p; } catch (e) { return null; } }
function bytesToBase64(bytes) { var s=''; for (var i=0;i=0){ type=parseInt(s.substring(0,dotIdx),10); rest=s.substring(dotIdx+1); }
else{ var pp=s.split('|'); type=(pp.length===3)?2:0; rest=s; }
var parts=rest.split('|');
if(type===2&&parts.length===3) return {type:2,iv:base64ToBytes(parts[0]),ct:base64ToBytes(parts[1]),mac:base64ToBytes(parts[2])};
if((type===0||type===1||type===4)&&parts.length>=2) return {type:type,iv:base64ToBytes(parts[0]),ct:base64ToBytes(parts[1]),mac:null};
return null;
}
async function decryptAesCbc(data,key,iv){ var ck=await crypto.subtle.importKey('raw',key,{name:'AES-CBC'},false,['decrypt']); return new Uint8Array(await crypto.subtle.decrypt({name:'AES-CBC',iv:iv},ck,data)); }
async function decryptBw(cipherString,encKey,macKey){
var parsed=parseCipherString(cipherString); if(!parsed) return null;
if(parsed.type===2&&macKey&&parsed.mac){
var macData=concatBytes(parsed.iv,parsed.ct); var computedMac=await hmacSha256(macKey,macData);
var match=true; if(computedMac.length!==parsed.mac.length) match=false;
else{ for(var i=0;i0) state.selectedCipherId=state.ciphers[0].id; await decryptVault(); }
async function loadAdminData(){ if(!state.profile||state.profile.role!=='admin') return; var u=await authFetch('/api/admin/users',{method:'GET'}); if(u.ok){ var uj=await u.json(); state.users=uj.data||[]; } var i=await authFetch('/api/admin/invites?includeInactive=true',{method:'GET'}); if(i.ok){ var ij=await i.json(); state.invites=ij.data||[]; } }
function selectedCount(){ var n=0; for(var k in state.selectedMap){ if(state.selectedMap[k]) n++; } return n; }
function cipherTypeKey(c){
var tnum=Number(c&&c.type);
if(tnum===1) return 'login';
if(tnum===3) return 'card';
if(tnum===4) return 'identity';
if(tnum===2) return 'note';
return 'other';
}
function cipherTypeLabel(c){
var k=cipherTypeKey(c);
if(k==='login') return t('typeLogin');
if(k==='card') return t('typeCard');
if(k==='identity') return t('typeIdentity');
if(k==='note') return t('typeNote');
return t('typeOther');
}
function folderNameById(id){
for(var i=0;i>>0, false);
var key=await crypto.subtle.importKey('raw', keyBytes, {name:'HMAC', hash:'SHA-1'}, false, ['sign']);
var sig=new Uint8Array(await crypto.subtle.sign('HMAC', key, buf));
var offset=sig[sig.length-1]&0x0f;
var bin=((sig[offset]&0x7f)<<24)|((sig[offset+1]&0xff)<<16)|((sig[offset+2]&0xff)<<8)|(sig[offset+3]&0xff);
var token=String(bin%1000000).padStart(6,'0');
return { token: token.slice(0,3)+' '+token.slice(3), remain: remain };
}
async function updateLiveTotpDisplay(){
if(state.phase!=='app'||state.tab!=='vault') return;
if(state.totpTickBusy) return;
var vEl=document.getElementById('totp-live-value');
var rEl=document.getElementById('totp-live-remain');
if(!vEl||!rEl) return;
var c=selectedCipher(); if(!c||!c.login) return;
var raw=(c.login.decTotp||c.login.totp||'').trim();
if(!raw){ vEl.textContent=''; rEl.textContent=''; return; }
state.totpTickBusy=true;
try{
var x=await calcTotpNow(raw);
if(!x){ vEl.textContent='N/A'; rEl.textContent=''; return; }
vEl.textContent=x.token;
rEl.textContent=t('totpLiveIn')+': '+x.remain+'s';
}catch(e){
vEl.textContent='N/A';
rEl.textContent='';
}finally{
state.totpTickBusy=false;
}
}
function ensureTotpTicker(){
if(state.totpTicking) return;
state.totpTicking=true;
setInterval(function(){ updateLiveTotpDisplay(); }, 1000);
}
function filteredCiphers(){
var out=[]; var q=String(state.vaultQuery||'').toLowerCase();
for(var i=0;i=64) return { enc: raw.slice(0,32), mac: raw.slice(32,64), key: cipher.key };
}catch(e){}
}
return { enc: user.enc, mac: user.mac, key: null };
}
async function encryptTextValue(v, enc, mac){
var s=String(v==null?'':v);
if(!s) return null;
return encryptBw(new TextEncoder().encode(s), enc, mac);
}
function openCreateDraft(){
state.detailMode='create';
state.showSelectedPassword=true;
state.createMenuOpen=false;
state.detailDraft={
id: '',
type: 1,
name: '',
folderId: state.folderFilterId&&state.folderFilterId!==NO_FOLDER_FILTER?state.folderFilterId:'',
reprompt: false,
loginUsername: '',
loginPassword: '',
loginTotp: '',
websites: [''],
cardholderName: '',
cardNumber: '',
cardBrand: '',
cardExpMonth: '',
cardExpYear: '',
cardCode: '',
identTitle: '',
identFirstName: '',
identMiddleName: '',
identLastName: '',
identUsername: '',
identCompany: '',
identSsn: '',
identPassportNumber: '',
identLicenseNumber: '',
identEmail: '',
identPhone: '',
identAddress1: '',
identAddress2: '',
identAddress3: '',
identCity: '',
identState: '',
identPostalCode: '',
identCountry: '',
sshPrivateKey: '',
sshPublicKey: '',
sshFingerprint: '',
customFields: [],
notes: ''
};
}
function openEditDraft(cipher){
if(!cipher) return;
var login=cipher.login||{};
var uris=Array.isArray(login.uris)?login.uris:[];
var ws=[]; for(var i=0;i'+l+''; }
return opt('text',t('fieldText'))+opt('hidden',t('fieldHidden'))+opt('boolean',t('fieldBoolean'))+opt('linked',t('fieldLinked'));
}
function renderCreateMenu(){
if(!state.createMenuOpen) return '';
return '';
}
function renderFieldModal(){
if(!state.fieldModalOpen) return '';
return ''
+ '
'+t('addField')+'
'
+ ''
+ ''
+ ''
+ '
'
+ '
';
}
function fieldTypeTextByNum(n){
var x=parseFieldType(n);
if(x===1) return t('fieldHidden');
if(x===2) return t('fieldBoolean');
if(x===3) return t('fieldLinked');
return t('fieldText');
}
function renderCardBrandOptions(selected){
var s=String(selected||'').toLowerCase();
var brands=['','visa','mastercard','amex','discover','jcb','unionpay','dinersclub','maestro'];
var labels={ '':'-- Select --', visa:'Visa', mastercard:'Mastercard', amex:'American Express', discover:'Discover', jcb:'JCB', unionpay:'UnionPay', dinersclub:'Diners Club', maestro:'Maestro' };
var out='';
for(var i=0;i'+labels[b]+'';
}
return out;
}
function renderMonthOptions(selected){
var s=String(selected||'');
var out='';
for(var m=1;m<=12;m++){
var mm=m<10?('0'+m):String(m);
out += '';
}
return out;
}
function renderDraftTypeCards(d){
var typeNum=Number(d&&d.type||1);
if(typeNum===3){
return ''
+ '
Card details
'
+ '
Cardholder name
'
+ '
Number
'
+ '
Brand
'
+ '
Exp month
Exp year
'
+ '
Security code (CVV)
'
+ '
';
}
if(typeNum===4){
return ''
+ '
Personal details
'
+ '
Title
'
+ '
First name
'
+ '
Middle name
'
+ '
Last name
'
+ '
Username
'
+ '
Company
'
+ '
'
+ '
Identity
'
+ '
SSN
'
+ '
Passport number
'
+ '
License number
'
+ '
'
+ '
Contact information
'
+ '
Email
'
+ '
Phone
'
+ '
'
+ '
Address
'
+ '
Address 1
'
+ '
Address 2
'
+ '
Address 3
'
+ '
City / Town
'
+ '
State / Province
'
+ '
ZIP / Postal code
'
+ '
Country
'
+ '
';
}
if(typeNum===5){
return ''
+ '
SSH key
'
+ '
Private key
'
+ '
Public key
'
+ '
Fingerprint
'
+ '
';
}
if(typeNum===2){
return '';
}
return ''
+ '
'+t('credentials')+'
'
+ '
Username
'
+ '
Password
'
+ '
TOTP Secret
'
+ '
';
}
function renderReadOnlyCustomFields(cipher){
var fs=Array.isArray(cipher&&cipher.fields)?cipher.fields:[];
if(!fs.length) return '';
var rows='';
for(var i=0;i
';
}
function renderReadOnlyTypeDetails(c0, folderLabel, created, updated){
var typeNum=Number(c0&&c0.type||1);
var notes=c0&&((c0.decNotes||c0.notes)||'');
var baseHead=''
+ '
'
+ renderReadOnlyCustomFields(c0)
+ history;
}
var login=c0.login||{};
var username=login.decUsername||login.username||'';
var rawPwd=login.decPassword||login.password||'';
var masked=rawPwd?new Array(Math.max(rawPwd.length,12)+1).join('•'):'';
var pwdText=state.showSelectedPassword?rawPwd:masked;
var totp=login.decTotp||login.totp||'';
var uri0=firstCipherUri(c0);
return baseHead
+ '
';
}
function renderRegisterScreen(){
return ''
+ '
'
+ '
'+t('langSwitch')+'
'
+ '
'
+ '
'
+ ' '
+ '
'+t('register')+'
'
+ '
'+t('brand')+'
'
+ '
'
+ renderMsg()
+ ' '
+ ' '
+ '
'
+ '
';
}
function renderVaultTab(){
var list=filteredCiphers();
function renderFolderOptions(selectedId){
var html='';
for(var fi=0;fi'+esc(ff.decName||ff.name||ff.id)+'';
}
return html;
}
var rows='';
for(var i=0;i🌐')
: '🌐';
rows += ''
+ '
'
+ ''
+ '
'+icon+'
'+esc(nameText)+'
'+esc(subtitle||'')+'
'
+ '
';
}
if(!rows) rows='
'+t('noItems')+'
';
var c0=selectedCipher();
var detail='
'+t('selectItem')+'
';
if(state.detailMode==='create'){
var dc=state.detailDraft||{};
var wsHtml=''; var cws=Array.isArray(dc.websites)?dc.websites:[''];
for(var wci=0;wci
'+t('website')+' (URI)
'+(cws.length>1?'':'')+'';
}
var cfHtml=''; var cfs=Array.isArray(dc.customFields)?dc.customFields:[];
for(var cfi=0;cfi
';
} else if(c0){
var folderLabel=c0.folderId?folderNameById(c0.folderId):t('noFolder');
var updated=c0.revisionDate||c0.updatedAt||'';
var created=c0.creationDate||c0.createdAt||'';
if(state.detailMode==='edit'){
var de=state.detailDraft||{};
var ewsHtml=''; var ews=Array.isArray(de.websites)?de.websites:[''];
for(var wei=0;wei
'+t('website')+' (URI)
'+(ews.length>1?'':'')+'';
}
var efsHtml=''; var efs=Array.isArray(de.customFields)?de.customFields:[];
for(var efi=0;efi
';
}
function renderSettingsTab(){
var p=state.profile||{};
var secret=currentTotpSecret();
var qr='https://api.qrserver.com/v1/create-qr-code/?size=180x180&data='+encodeURIComponent(buildTotpUri(secret));
return ''
+ renderMsg()
+ '