const $=id=>document.getElementById(id);let panel=false,vaultPanel=false,lastState,vaultState,vaultBusy=false,vaultRevision=0; window.safetynet.onVaultHandoff(()=>{vaultRevision++;vaultPanel=false;$('vault').hidden=true;$('passwords').setAttribute('aria-expanded','false');clearVaultFields();$('vault-list').replaceChildren();}); let sidebarOpen=false,startPageOpen=false,overviewOpen=false,tabs=[],activeTab=null,groupFilter='',navigationRevision=0,pendingNavigation=null,lastLayoutKey='',lastTabsKey='',lastLibraryKey='',nativeTabsRevision=-1; const preference=(key,fallback)=>{try{return JSON.parse(localStorage.getItem(`sbrowser.${key}`))??fallback;}catch{return fallback;}}; const remember=(key,value)=>{try{localStorage.setItem(`sbrowser.${key}`,JSON.stringify(value));}catch{}}; const safeURL=value=>{try{const u=new URL(value);if(!['https:','http:'].includes(u.protocol)||u.username||u.password)return null;u.search='';u.hash='';return u.href;}catch{return null;}}; const metadata=list=>Array.isArray(list)?list.slice(0,100).filter(v=>v&&safeURL(v.url)&&typeof v.title==='string').map(v=>({url:safeURL(v.url),title:v.title.slice(0,160)})):[]; // Company artwork is signed policy data, never a remote image or executable SVG. function renderCompany(s){ const name=s.brand?.name==='SafetyNet'?'S Browser':s.brand?.name||'S Browser',logo=typeof s.brand?.logoDataUrl==='string'&&/^data:image\/png;base64,[A-Za-z0-9+/=]+$/.test(s.brand.logoDataUrl)&&s.brand.logoDataUrl.length<=180000?s.brand.logoDataUrl:null; document.documentElement.style.setProperty('--company-accent',/^#[0-9a-f]{6}$/i.test(s.brand?.color||'')?s.brand.color:'var(--accent)'); const setLogo=(image)=>{image.hidden=!logo;if(logo)image.src=logo;else image.removeAttribute('src');image.alt=logo?`${name} logo`:'';}; setLogo($('start-company-logo'));$('start-brand-name').textContent=name; const monogram=document.querySelector('.brand-monogram');monogram.replaceChildren();if(logo){const image=document.createElement('img');image.className='company-logo';setLogo(image);monogram.append(image);}else monogram.textContent=name.charAt(0).toUpperCase(); for(const id of ['welcome','settings','ai','vault']){const host=$(id);if(!host)continue;let mark=host.querySelector('.company-surface-brand');if(!mark){mark=document.createElement('div');mark.className='company-surface-brand';mark.append(document.createElement('img'),document.createElement('span'));host.prepend(mark);}setLogo(mark.firstElementChild);mark.firstElementChild.className='company-logo';mark.lastElementChild.textContent=name;} const start=s.startPage||{};$('start-title').textContent=start.title||'Your space to explore.';$('start-subtitle').textContent=start.subtitle||'';$('start-subtitle').hidden=!start.subtitle; $('company-news').hidden=!start.newsEnabled;const list=$('company-news-items');list.replaceChildren(); if(start.newsEnabled)for(const item of (Array.isArray(start.news)?start.news:[]).slice(0,12)){let url;try{url=new URL(item.url);if(url.protocol!=='https:'||url.username||url.password)continue;}catch{continue;}if(typeof item.title!=='string')continue;const card=button('',()=>navigateTab(url.href),'company-news-card'),title=document.createElement('strong');title.textContent=item.title.slice(0,240);card.append(title);if(item.publishedAt){const date=new Date(item.publishedAt);if(Number.isFinite(date.getTime())){const time=document.createElement('time');time.dateTime=date.toISOString();time.textContent=date.toLocaleDateString();card.append(time);}}list.append(card);} $('company-news-empty').hidden=!!list.children.length; } let bookmarks=[],readingList=metadata(preference('reading-list',[])),groups=[]; function currentTab(){return tabs.find(t=>t.id===activeTab)||{id:null,url:'',title:'New Tab',group:'',startPage:true};} async function updateChrome(){ document.body.classList.toggle('sidebar-open',sidebarOpen);if($('library'))$('library').hidden=!sidebarOpen;if($('start-page'))$('start-page').hidden=!startPageOpen||overviewOpen||!lastState?.ready;if($('overview'))$('overview').hidden=!overviewOpen||!lastState?.ready; for(const id of ['bookmark','reading-add','share-url'])if($(id))$(id).disabled=!lastState?.ready||startPageOpen||overviewOpen||!currentTab().url||(id==='bookmark'&&(!sectionEditable('library')||!lastState?.bookmarks?.allowUserAdd))||(id==='reading-add'&&!sectionEditable('library')); const height=Math.ceil($('chrome')?.getBoundingClientRect().bottom||104),layout={height,sidebarOpen,startPageOpen:startPageOpen&&!!lastState?.ready,overviewOpen:overviewOpen&&!!lastState?.ready,toolsOpen:!!($('tools-menu')&&!$('tools-menu').hidden)||!!$('bookmark-editor')?.open||!!$('bookmark-manager')?.open||!!$('agent-pairing')?.open},key=JSON.stringify(layout);document.documentElement.style.setProperty('--chrome-height',`${height}px`);if(window.safetynet.setChromeLayout&&key!==lastLayoutKey){lastLayoutKey=key;try{await call('setChromeLayout',layout);}catch(e){lastLayoutKey='';throw e;}} } async function navigateTab(value){ const token=++navigationRevision,tab=currentTab();if(!tab.id)throw Error('No managed tab is available');pendingNavigation={token,tabId:tab.id};overviewOpen=false; try{const result=await call('navigate',{url:value,tabId:tab.id});if(token===navigationRevision)show(result);} finally{if(pendingNavigation?.token===token)pendingNavigation=null;} } // Native WebContents own identity, history and lifetime. Switching never reloads a URL. async function selectTab(id){if(!tabs.some(t=>t.id===id))return;navigationRevision++;overviewOpen=false;show(await call('activateTab',id));$('address').value=currentTab().startPage?'':currentTab().url;await updateChrome();} async function newTab(){navigationRevision++;overviewOpen=false;show(await call('createTab',{group:groupFilter}));$('address').value=currentTab().startPage?'':currentTab().url;await updateChrome();$('address').focus();} async function closeTab(id){if(!tabs.some(t=>t.id===id))return;navigationRevision++;const previous=activeTab;show(await call('closeTab',id));if(previous!==activeTab)$('address').value=currentTab().startPage?'':currentTab().url;await updateChrome();} function button(text,fn,cls=''){const b=document.createElement('button');b.type='button';b.className=cls;b.textContent=text;b.onclick=task(fn);return b;} function renderTabs(){ const strip=$('tab-strip');if(!strip)return;const key=JSON.stringify({tabs,activeTab,groupFilter,ready:!!lastState?.ready});if(key===lastTabsKey)return;lastTabsKey=key;strip.replaceChildren();for(const tab of tabs.filter(t=>!groupFilter||t.group===groupFilter)){ const el=document.createElement('div');el.className=`browser-tab${tab.id===activeTab?' active':''}`;el.setAttribute('role','tab');el.setAttribute('aria-selected',String(tab.id===activeTab));el.title=tab.url||'New Tab'; const select=button(tab.title||'New Tab',()=>selectTab(tab.id),'tab-label'),close=button('×',()=>closeTab(tab.id),'tab-close');close.setAttribute('aria-label',`Close ${tab.title}`);select.disabled=!lastState?.ready;close.disabled=!lastState?.ready;el.dataset.tabId=tab.id;el.append(select,close);strip.append(el);} if($('overview-grid')){$('overview-grid').replaceChildren();for(const tab of tabs){const card=button('',()=>selectTab(tab.id),'overview-card');const title=document.createElement('strong'),url=document.createElement('span');title.textContent=tab.title;url.textContent=safeURL(tab.url)||'Favorites and a fresh start';card.append(title,url);$('overview-grid').append(card);}} } function renderLinks(id,items){if(!$(id))return;$(id).replaceChildren();for(const item of items){const b=button('',()=>navigateTab(item.url),'favorite-card');const initial=document.createElement('span'),label=document.createElement('strong');initial.className='favorite-icon';initial.textContent=new URL(item.url).hostname.charAt(0).toUpperCase();label.textContent=item.title||new URL(item.url).hostname;b.title=item.url;b.append(initial,label);const badge=document.createElement('small');badge.className='favorite-badge';badge.textContent=item.managed?'Company':'';b.append(badge);$(id).append(b);}} let bookmarkEditingId=null; async function openBookmarkEditor(item){ requireSection('library',true);const rules=lastState?.bookmarks; if(item?.managed||!(item?rules?.allowUserEdit:rules?.allowUserAdd))throw Error('This bookmark is managed by your administrator'); bookmarkEditingId=item?.id||null;$('bookmark-editor-title').textContent=item?'Edit bookmark':'Add bookmark'; $('bookmark-title').value=(item?.title||lastState?.pageTitle||'New bookmark').slice(0,120);$('bookmark-url').value=item?.url||safeURL(lastState?.url)||'';$('bookmark-folder').value=item?.folder||''; $('bookmark-editor-error').textContent='';$('bookmark-editor').showModal();await updateChrome();$('bookmark-title').focus(); } async function mutateBookmark(action,bookmark,id){ const snapshot=lastState;const result=await call('mutateBookmarks',{action,...(bookmark?{bookmark}:{}),...(id?{id}:{}),expectedRevision:snapshot.bookmarks.personal.revision,policyVersion:snapshot.policyVersion});show({...snapshot,...result}); } function renderBookmarkLibrary(){ const target=$('library-bookmarks');target.replaceChildren(); for(const [managed,label] of [[true,'Company bookmarks (managed)'],[false,'Your bookmarks']]){ const heading=document.createElement('p');heading.className='bookmark-section-label';heading.textContent=label;target.append(heading); let folder=null;for(const item of bookmarks.filter(item=>!!item.managed===managed).sort((a,b)=>(a.folder||'').localeCompare(b.folder||''))){ if(item.folder&&item.folder!==folder){const group=document.createElement('small');group.textContent=item.folder;target.append(group);}folder=item.folder; const row=document.createElement('div');row.className='bookmark-row';const link=button(item.title,()=>navigateTab(item.url),'bookmark-link');link.title=item.url;row.append(link); if(!managed&§ionEditable('library')){if(lastState?.bookmarks?.allowUserEdit){const edit=button('Edit',()=>openBookmarkEditor(item),'bookmark-action');edit.setAttribute('aria-label',`Edit ${item.title}`);row.append(edit);}if(lastState?.bookmarks?.allowUserDelete){const remove=button('Remove',()=>mutateBookmark('delete',null,item.id),'bookmark-action');remove.setAttribute('aria-label',`Remove ${item.title}`);row.append(remove);}} target.append(row); } } const legacy=metadata(preference('bookmarks',[])); if(legacy.length&§ionVisible('library')){const heading=document.createElement('p');heading.className='bookmark-section-label';heading.textContent='Previous local bookmarks';target.append(heading);const note=document.createElement('small');note.textContent='These existing links remain local. Add them to your bookmarks to sync them when company policy permits.';target.append(note);for(const item of legacy){const link=button(item.title,()=>navigateTab(item.url),'bookmark-link');link.title=item.url;target.append(link);}} } $('bookmark-cancel').onclick=()=>$('bookmark-editor').close(); $('bookmark-editor').addEventListener('close',()=>updateChrome().catch(()=>{})); $('bookmark-form').onsubmit=async event=>{event.preventDefault();const submit=$('bookmark-save');submit.disabled=true;try{ requireSection('library',true);await mutateBookmark(bookmarkEditingId?'update':'add',{id:bookmarkEditingId||crypto.randomUUID(),title:$('bookmark-title').value,url:$('bookmark-url').value,folder:$('bookmark-folder').value});$('bookmark-editor').close(); }catch(e){$('bookmark-editor-error').textContent=e.message;}finally{submit.disabled=false;}}; function renderLibrary(){ const key=JSON.stringify({bookmarks,bookmarkPermissions:lastState?.bookmarks,libraryAI:lastState?.libraryAI,settings:lastState?.settings,readingList,groups,tabs,activeTab});if(key===lastLibraryKey)return;lastLibraryKey=key; const favorites=sectionVisible('library')?bookmarks.slice(0,12):[];renderLinks('favorites-grid',favorites);renderLinks('library-favorites',favorites);renderBookmarkLibrary();if($('bookmark-manager')?.open){libraryProposal=null;$('manager-proposal').hidden=true;renderBookmarkManager();}renderLinks('library-reading',readingList); if($('library-groups')){$('library-groups').replaceChildren();$('library-groups').append(button('All tabs',()=>{groupFilter='';renderTabs();},'group-item'));for(const group of groups){const row=document.createElement('div');row.className='group-manager-row';row.append(button(group,()=>{groupFilter=group;renderTabs();},'group-item'));if(sectionEditable('library')){const rename=button('Rename',async()=>{const label=$('group-name').value.trim();if(!label||label.length>40)throw Error('Enter a new group name, up to 40 characters');for(const tab of [...tabs].filter(t=>t.group===group))show(await call('groupTab',{id:tab.id,group:label}));groupFilter=label;renderTabs();});rename.setAttribute('aria-label',`Rename group ${group}`);const remove=button('Ungroup',async()=>{for(const tab of [...tabs].filter(t=>t.group===group))show(await call('groupTab',{id:tab.id,group:''}));groupFilter='';renderTabs();});remove.setAttribute('aria-label',`Ungroup tabs in ${group}`);row.append(rename,remove);}$('library-groups').append(row);}} if($('favorites-empty'))$('favorites-empty').hidden=!!favorites.length; if($('library-tabs')){$('library-tabs').replaceChildren();for(const tab of tabs)$('library-tabs').append(button(tab.title,()=>selectTab(tab.id),`library-tab${tab.id===activeTab?' active':''}`));} renderLinks('recent-list',tabs.filter(t=>t.url).slice(-5).map(t=>({url:safeURL(t.url),title:t.title})).filter(t=>t.url)); } const systemAppearance=window.matchMedia('(prefers-color-scheme: dark)'); const systemIsDark=()=>window.matchMedia('(prefers-color-scheme: dark)').matches; const accentPresets={blue:{base:'#0969da',light:'#0969da',dark:'#75baff'},teal:{base:'#007d76',light:'#00736d',dark:'#6cdbca'},violet:{base:'#7042c1',light:'#7042c1',dark:'#c5a4ff'}}; function applyAppearance(){ // Appearance comes exclusively from the privileged process, including unlocked saved choices. const appearance=lastState?.settings?.appearance||{mode:'system',accent:'blue',tabMode:'separate'},theme=appearance.mode==='system'?'auto':appearance.mode,mode=appearance.tabMode; const effective=theme==='auto'?(systemIsDark()?'dark':'light'):theme,savedAccent=appearance.accent,accent=typeof savedAccent==='string'&&Object.hasOwn(accentPresets,savedAccent)?savedAccent:'blue'; document.documentElement.dataset.theme=theme;document.documentElement.dataset.effectiveTheme=effective;document.documentElement.dataset.accent=accent;document.documentElement.dataset.tabMode=mode==='compact'?'compact':'separate'; document.body.dataset.theme=effective;document.body.dataset.tabMode=document.documentElement.dataset.tabMode;document.documentElement.style.colorScheme=effective; document.documentElement.style.setProperty('--accent',accentPresets[accent].base);document.documentElement.style.setProperty('--accent-text',accentPresets[accent][effective]); if($('theme-mode'))$('theme-mode').value=theme;if($('tab-mode'))$('tab-mode').value=document.documentElement.dataset.tabMode;if($('accent-mode'))$('accent-mode').value=accent; if($('theme-status'))$('theme-status').textContent=theme==='auto'?`System · ${effective==='dark'?'Dark':'Light'}. Changes follow your device.`:`${effective==='dark'?'Dark':'Light'} theme · browser workspace only.`; updateChrome().catch(()=>{}); } systemAppearance.addEventListener('change',()=>{if((!lastState?.settings||lastState.settings.appearance.mode==='system'))applyAppearance();}); function refreshSystemAppearance(){if((!lastState?.settings||lastState.settings.appearance.mode==='system')&&document.body.dataset.theme!==(systemIsDark()?'dark':'light'))applyAppearance();} window.addEventListener('focus',refreshSystemAppearance); document.addEventListener('visibilitychange',()=>{if(!document.hidden)refreshSystemAppearance();}); // Some Windows compositor/theme transitions update the query without a change event. setInterval(refreshSystemAppearance,1000); // Hidden controls are removed from keyboard navigation as well as visual presentation. // Native IPC repeats every check: DOM visibility is never the security boundary. function sectionVisible(name){const settings=lastState?.settings;return !!settings?.available&&(settings.visible||['library','ai','passwords','documentScan','agents'].includes(name))&&!settings.hiddenSections.includes(name);} function sectionEditable(name){return sectionVisible(name)&&(name!=='library'||lastState.settings.visible)&&!lastState.settings.lockedSections.includes(name);} function requireSection(name,edit=false){if(!(edit?sectionEditable(name):sectionVisible(name)))throw Error('This action is managed by your administrator.');} function applyManagedSettings(){ const settings=lastState?.settings,visible=!!settings?.available&&settings.visible; $('tools-toggle').hidden=!visible;$('tools-toggle').disabled=!visible; if(!visible){$('tools-menu').hidden=true;$('tools-toggle').setAttribute('aria-expanded','false');} const sections={appearance:['appearance-section'],library:['bookmark','reading-add','sidebar-toggle','bookmark-manager-open','favorites-manage'],ai:['ai-toggle','sidebar'],passwords:['passwords'],documentScan:['inspect'],agents:['agents']}; for(const [name,ids] of Object.entries(sections))for(const id of ids){const el=$(id);if(!el)continue;el.hidden=!sectionVisible(name);if(!sectionVisible(name)||(['library','ai','passwords','documentScan','agents'].includes(name)&&!sectionEditable(name)))el.disabled=true;} for(const id of ['theme-mode','accent-mode','tab-mode'])$(id).disabled=!sectionEditable('appearance'); for(const id of ['bookmark','reading-add','group-add','group-name'])if($(id))$(id).disabled=!sectionEditable('library')||!lastState?.ready||(id==='bookmark'&&!lastState?.bookmarks?.allowUserAdd); $('send').disabled=!lastState?.aiEnabled||!sectionEditable('ai');$('prompt').disabled=!sectionEditable('ai'); for(const [quick,original,name,enabled] of [['quick-ai','ai-toggle','ai',lastState?.aiEnabled],['quick-passwords','passwords','passwords',lastState?.ready&&lastState?.passwordManagerEnabled!==false],['quick-inspect','inspect','documentScan',lastState?.documentScanEnabled],['quick-agents','agents','agents',lastState?.agentsEnabled]]){const el=$(quick);el.hidden=visible||!sectionVisible(name)||!enabled;el.disabled=!sectionEditable(name)||!enabled;el.setAttribute('aria-expanded',$(original).getAttribute('aria-expanded')||'false');} $('settings-managed').hidden=!sectionVisible('appearance')||sectionEditable('appearance'); if(!sectionVisible('library')){if($('bookmark-manager')?.open)$('bookmark-manager').close();sidebarOpen=false;$('sidebar-toggle').setAttribute('aria-expanded','false');} if(!sectionEditable('ai')&&panel)closeAI().catch(()=>{}); if(!sectionEditable('passwords')&&vaultPanel)closeVault().catch(()=>{}); if($('agent-pairing')?.open&&(!lastState?.ready||!lastState?.agentsEnabled||!sectionEditable('agents')))$('agent-pairing').close(); applyAppearance(); } async function call(method,...args){const r=await window.safetynet[method](...args);if(!r.ok)throw Error(r.error);return r.data;} function clearVaultFields(){for(const id of ['vault-master','vault-confirm','vault-password','vault-origin','vault-label','vault-username'])$(id).value='';} async function closeVault(){vaultRevision++;vaultPanel=false;$('vault').hidden=true;$('passwords').setAttribute('aria-expanded','false');clearVaultFields();$('vault-list').replaceChildren();$('vault-add').open=false;await call('vaultLock');await call('toggleVault',false);} async function closeAI(){panel=false;$('ai').hidden=true;for(const id of ['sidebar','ai-toggle'])$(id).setAttribute('aria-expanded','false');await call('toggleAI',false);} async function openAI(){requireSection('ai',true);if(vaultPanel)await closeVault();panel=true;$('ai').hidden=false;for(const id of ['sidebar','ai-toggle'])$(id).setAttribute('aria-expanded','true');await call('toggleAI',true);} function show(s){ // A delayed invoke response must not replace a newer tab switch or navigation event. if(Number.isSafeInteger(s.tabsRevision)&&s.tabsRevisiont.group).filter(Boolean))];activeTab=s.activeTabId;startPageOpen=!!currentTab().startPage;if(groupFilter&¤tTab().group!==groupFilter)groupFilter=currentTab().group;} document.body.classList.toggle('window-maximized',!!s.windowMaximized); lastState=s;renderCompany(s);bookmarks=[...(s.bookmarks?.managed||[]).map(item=>({...item,managed:true})),...(s.bookmarks?.personal?.items||[])];if(!s.ready||!sectionEditable('library')||($('bookmark-editor').open&&!(bookmarkEditingId?s.bookmarks?.allowUserEdit:s.bookmarks?.allowUserAdd)))$('bookmark-editor').close();const brand=s.brand.name==='SafetyNet'?'S Browser':s.brand.name;$('brand').textContent=brand;document.title=brand;$('managed').textContent=s.ready?(s.loginCheckPending?'Login check required':`Managed · policy ${s.policyVersion}`):'Access paused';$('managed').parentElement.classList.toggle('paused',!s.ready); $('welcome').hidden=s.ready;$('enrollment').hidden=s.enrolled;$('message').textContent=s.message; if(document.activeElement!==$('address'))$('address').value=startPageOpen?'':s.url||''; $('managed').title=s.message;$('address-form').querySelector('.address-lock').src=s.url?.startsWith('https:')?'icons/lock-keyhole.svg':'icons/globe.svg'; $('ai-toggle').disabled=!s.aiEnabled;$('sidebar').disabled=!s.ready;$('send').disabled=!s.aiEnabled; $('agents').disabled=!s.agentsEnabled;$('inspect').disabled=!s.documentScanEnabled;$('passwords').disabled=!s.ready||s.passwordManagerEnabled===false; $('inspect').title=s.documentScanEnabled?'Inspect a document locally':'Document analysis is disabled by company policy'; $('back').disabled=!s.ready||!currentTab().canGoBack;$('forward').disabled=!s.ready||!currentTab().canGoForward;$('reload').disabled=!s.ready||currentTab().startPage;$('address').disabled=!(s.canNavigate??s.ready); if(vaultPanel&&s.vaultLocked===true&&vaultState?.locked!==true){vaultRevision++;clearVaultFields();$('vault-list').replaceChildren();$('vault-open').hidden=true;vaultState={...vaultState,locked:true};refreshVault().catch(()=>{});} if(vaultPanel&&s.passwordManagerEnabled===false)closeVault().catch(()=>{}); if(!s.ready){if(panel)closeAI().catch(()=>{});if(vaultPanel)closeVault().catch(()=>{});clearVaultFields();} document.documentElement.style.setProperty('--page-tint',/^#[a-f0-9]{6}$/i.test(s.pageColor||'')?s.pageColor:'#b8c7dd'); if($('start-greeting'))$('start-greeting').textContent='A fresh place to begin'; if($('sidebar-toggle'))$('sidebar-toggle').disabled=!s.ready; if($('new-tab'))$('new-tab').disabled=!s.ready;if($('tab-overview'))$('tab-overview').disabled=!s.ready; reconcileAgentPairing();applyManagedSettings();updateChrome().catch(()=>{});renderTabs();renderLibrary(); } function task(fn){return async event=>{event?.preventDefault();$('error').textContent='';try{await fn();}catch(e){$('error').textContent=e.message;$('managed').textContent=e.message;}};} function vaultTask(fn){return async event=>{event?.preventDefault();if(vaultBusy)return;vaultBusy=true;$('vault-error').textContent='';try{await fn();}catch{$('vault-error').textContent='The vault action could not be completed. Check your password and company policy.';}finally{vaultBusy=false;$('vault-master').value='';$('vault-confirm').value='';}};} function input(){return {server:$('server').value,code:$('code').value};} $('enroll').onclick=task(async()=>{$('enroll').disabled=true;try{show(await call('enroll',input()));$('code').value='';}finally{$('enroll').disabled=false;}}); $('sso').onclick=task(async()=>{$('sso').disabled=true;try{show(await call('sso',input()));}finally{$('sso').disabled=false;}}); $('address-form').onsubmit=task(async()=>navigateTab($('address').value)); for(const method of ['back','forward','reload'])$(method).onclick=task(()=>call(method)); $('ai-toggle').onclick=$('sidebar').onclick=task(async()=>{if(panel)await closeAI();else await openAI();}); $('ai-close').onclick=task(closeAI); $('inspect').onclick=task(async()=>{ requireSection('documentScan',true);$('inspect').disabled=true;try{const result=await call('inspectFile');if(!result)return;await openAI();entry(`Inspection: ${result.name}\nDecision: ${result.decision}\nDetector: ${result.ruleId||'No match'}\nType: ${result.contentType}\nComplete coverage: ${result.coverage.complete}\n${result.model?`Model: ${result.model}`:''}\nAnalyzed locally. This result does not authorize a website upload.`);}finally{show(await call('state'));} }); function entry(text){const empty=$('conversation').querySelector('.empty-chat');empty?.remove();const e=document.createElement('div');e.className='entry';e.textContent=text;$('conversation').append(e);e.scrollIntoView();} // Pairing selects a signed central identity. It never creates a local grant or exposes tokens. let agentPairingRevision=0,agentPairingFingerprint=''; function agentGrantFingerprint(){return JSON.stringify({mode:lastState?.agentIdentityMode,grants:approvedAgentGrants()});} function reconcileAgentPairing(){if($('agent-pairing')?.open&&agentPairingFingerprint!==agentGrantFingerprint())$('agent-pairing').close();} setInterval(reconcileAgentPairing,1000); function approvedAgentGrants(){return (Array.isArray(lastState?.agentGrants)?lastState.agentGrants:[]).filter(g=>g&&typeof g.id==='string'&&typeof g.name==='string'&&Number.isSafeInteger(g.generation)&&g.generation>=1&&Date.parse(g.expiresAt)>Date.now());} function agentPairingDialog(){ let dialog=$('agent-pairing');if(dialog)return dialog; dialog=document.createElement('dialog');dialog.id='agent-pairing';dialog.setAttribute('aria-labelledby','agent-pairing-title'); const title=document.createElement('h2');title.id='agent-pairing-title';title.textContent='Connect a local agent'; const description=document.createElement('p');description.id='agent-pairing-description';dialog.setAttribute('aria-describedby',description.id); const list=document.createElement('div');list.id='agent-grant-list';list.className='agent-grant-list'; const result=document.createElement('pre');result.id='agent-pairing-result';result.setAttribute('role','status');result.setAttribute('aria-live','polite'); const close=button('Close',()=>dialog.close(),'secondary');close.setAttribute('aria-label','Close agent connection'); dialog.append(title,description,list,result,close);document.body.append(dialog); dialog.addEventListener('close',()=>{agentPairingRevision++;agentPairingFingerprint='';list.replaceChildren();result.textContent='';updateChrome().catch(()=>{});}); return dialog; } async function openAgentPairing(){ requireSection('agents',true);if(!lastState?.ready||!lastState?.agentsEnabled)throw Error('Local agent control is disabled by your administrator.'); const dialog=agentPairingDialog(),revision=++agentPairingRevision,mode=lastState.agentIdentityMode==='individual'?'individual':'legacy',list=$('agent-grant-list'),result=$('agent-pairing-result');list.replaceChildren();result.textContent=''; $('agent-pairing-description').textContent=mode==='individual'?'Choose an identity approved by your administrator. Each identity has its own permissions and encrypted pairing file.':'Shared legacy credential: all connected agents share the same permissions. Your administrator can require individual identities.'; const pair=async grant=>{ requireSection('agents',true);if(revision!==agentPairingRevision)return; if(mode==='individual'&&!approvedAgentGrants().some(g=>g.id===grant.id&&g.generation===grant.generation))throw Error('This agent identity is no longer approved.'); const connection=await call('agentConnection',grant?.id); requireSection('agents',true);if(revision!==agentPairingRevision||!dialog.open)return; if(!lastState?.ready||!lastState?.agentsEnabled||(lastState.agentIdentityMode==='individual'?'individual':'legacy')!==mode||(grant&&!approvedAgentGrants().some(g=>g.id===grant.id&&g.generation===grant.generation)))throw Error('Agent permissions changed. Open the connection dialog again.'); result.textContent=`${grant?`Identity: ${grant.name}\n`:'Shared legacy credential\n'}Endpoint: ${connection.address}\nEncrypted pairing file: ${connection.credentialPath}\nUse the trusted local helper. Every action follows company policy.`; }; if(mode==='individual'){ const grants=approvedAgentGrants();if(!grants.length){const empty=document.createElement('p');empty.textContent='No active agent identities are approved. Ask your administrator to publish an agent grant.';list.append(empty);} for(const grant of grants){const choice=button('',()=>pair(grant),'agent-grant-choice'),name=document.createElement('strong'),detail=document.createElement('small');name.textContent=grant.name;detail.textContent=`Expires ${new Date(grant.expiresAt).toLocaleString()} · generation ${grant.generation}`;choice.append(name,detail);list.append(choice);} }else list.append(button('Get shared legacy connection',()=>pair(null),'agent-grant-choice')); agentPairingFingerprint=agentGrantFingerprint();dialog.showModal();await updateChrome(); } $('agents').onclick=task(openAgentPairing); $('ai-form').onsubmit=task(async()=>{const prompt=$('prompt').value;if(!prompt.trim())return;entry(`You: ${prompt}`);$('send').disabled=true;try{const result=await call('ai',prompt);entry(result.answer);$('prompt').value='';}catch(e){entry(e.message);}finally{$('send').disabled=!lastState?.aiEnabled;}}); async function refreshVault(){ const revision=++vaultRevision;const status=await call('vaultStatus');if(!vaultPanel||revision!==vaultRevision)return;const previous=vaultState;vaultState=status; $('vault-access').hidden=!status.enabled||!status.locked&&status.exists;$('vault-open').hidden=!status.enabled||status.locked||!status.exists; $('vault-access-submit').textContent=status.exists?'Unlock vault':'Create vault';$('vault-master').autocomplete=status.exists?'current-password':'new-password'; $('vault-confirm').hidden=status.exists;$('vault-confirm-label').hidden=status.exists;$('vault-confirm').required=!status.exists&&status.enabled; $('master-label').textContent=status.exists?'Master password':'Choose a master password'; $('vault-description').textContent=!status.enabled?'Password storage is disabled by your administrator.':status.locked||!status.exists?'Your encrypted logins stay on this device. Unlock with your master password.':`Unlocked · locks after ${status.idleLockSeconds} seconds of inactivity.`; $('vault-add').hidden=!status.allowSaving;$('vault-save-submit').disabled=!status.allowSaving;$('vault-generate').disabled=!status.allowSaving; if(!status.enabled||status.locked||!status.exists){if(!status.enabled||previous?.locked===false)clearVaultFields();$('vault-list').replaceChildren();return;} const entries=await call('vaultList');if(!vaultPanel||revision!==vaultRevision)return;$('vault-list').replaceChildren(); if(!entries.length){const empty=document.createElement('div');empty.className='empty-vault';empty.textContent='No saved logins yet.';$('vault-list').append(empty);} for(const item of entries){const card=document.createElement('div');card.className='login-card';const label=document.createElement('strong');label.textContent=item.label||item.origin;const origin=document.createElement('p');origin.textContent=item.origin;const username=document.createElement('p');username.textContent=item.username||'No username';const buttons=document.createElement('div');buttons.className='login-buttons';const fill=document.createElement('button');fill.textContent='Fill login';fill.disabled=!status.allowAutofill;fill.onclick=vaultTask(async()=>{await call('vaultFill',item.id);$('vault-error').textContent='Login filled. Submission remains subject to company DLP policy.';});const remove=document.createElement('button');remove.className='secondary';remove.textContent='Delete';remove.disabled=!status.allowSaving;remove.onclick=vaultTask(async()=>{await call('vaultDelete',item.id);await refreshVault();});const prepare=document.createElement('button');prepare.textContent='Prepare approved login';prepare.title='Fill and permit one approved login POST for up to 15 seconds. Submit on the website; other fields still pass company DLP.';prepare.hidden=!status.allowControlledSubmission;prepare.disabled=!status.allowAutofill;prepare.onclick=vaultTask(async()=>{await call('vaultPrepareSubmit',item.id);});buttons.append(fill,prepare,remove);card.append(label,origin,username,buttons);$('vault-list').append(card);} } $('passwords').onclick=vaultTask(async()=>{requireSection('passwords',true);if(vaultPanel){await closeVault();return;}await closeAI();vaultPanel=true;$('vault').hidden=false;$('passwords').setAttribute('aria-expanded','true');await call('toggleVault',true);await refreshVault();}); $('vault-close').onclick=vaultTask(closeVault); $('vault-access').onsubmit=vaultTask(async()=>{const exists=vaultState?.exists,master=$('vault-master').value;if(!exists&&master!==$('vault-confirm').value){$('vault-error').textContent='The master passwords do not match.';return;}await call(exists?'vaultUnlock':'vaultCreate',master);$('vault-master').value='';$('vault-confirm').value='';await refreshVault();}); $('vault-lock').onclick=vaultTask(async()=>{vaultRevision++;clearVaultFields();$('vault-list').replaceChildren();await call('vaultLock');await refreshVault();}); $('vault-save').onsubmit=vaultTask(async()=>{await call('vaultSave',{origin:$('vault-origin').value,label:$('vault-label').value,username:$('vault-username').value,password:$('vault-password').value});clearVaultFields();$('vault-add').open=false;await refreshVault();}); $('vault-generate').onclick=vaultTask(async()=>{const generated=await call('vaultGenerate');$('vault-password').value=typeof generated==='string'?generated:generated.password;}); $('vault-add').ontoggle=()=>{if($('vault-add').open&&lastState?.url&&!$('vault-origin').value){try{$('vault-origin').value=new URL(lastState.url).origin;}catch{}}else if(!$('vault-add').open)$('vault-password').value='';}; window.addEventListener('blur',()=>{vaultRevision++;clearVaultFields();$('vault-list').replaceChildren();if(vaultPanel)call('vaultLock').then(refreshVault).catch(()=>{});}); window.addEventListener('pagehide',()=>{clearVaultFields();if(vaultPanel)call('vaultLock').catch(()=>{});}); setInterval(()=>{if(vaultPanel&&!vaultBusy)refreshVault().catch(()=>{clearVaultFields();$('vault-list').replaceChildren();});},5000); function wire(id,handler){if($(id))$(id).onclick=task(handler);} wire('sidebar-toggle',async()=>{requireSection('library');sidebarOpen=!sidebarOpen;$('sidebar-toggle').setAttribute('aria-expanded',String(sidebarOpen));await updateChrome();}); wire('new-tab',newTab);wire('tab-overview',async()=>{overviewOpen=!overviewOpen;startPageOpen=!!currentTab().startPage;$('tab-overview').setAttribute('aria-expanded',String(overviewOpen));renderTabs();await updateChrome();}); wire('overview-close',async()=>{overviewOpen=false;startPageOpen=!!currentTab().startPage;await updateChrome();}); for(const action of ['minimize','maximize','close'])wire(`window-${action}`,()=>call('windowControls',action)); wire('share-url',()=>call('shareURL')); wire('bookmark',async()=>{requireSection('library',true);if(!lastState?.bookmarks?.allowUserAdd)throw Error('Adding bookmarks is disabled by your administrator');await openBookmarkEditor();}); wire('reading-add',async()=>{requireSection('library',true);await call('authorizeSetting','library');requireSection('library',true);if(!lastState?.ready)throw Error('Open an approved page first');const url=safeURL(lastState.url);if(!url)throw Error('This page cannot be saved');if(!readingList.some(b=>b.url===url)){readingList.push({url,title:String(lastState.pageTitle||new URL(url).hostname).slice(0,160)});readingList=readingList.slice(-100);remember('reading-list',readingList);renderLibrary();}}); wire('group-add',async()=>{requireSection('library',true);await call('authorizeSetting','library');requireSection('library',true);const field=$('group-name'),label=field?.value.trim();if(!label)throw Error('Enter a tab group name');if(label.length>40)throw Error('Tab group names are limited to 40 characters');if(!groups.includes(label)){if(groups.length>=20)throw Error('At most 20 tab groups are supported');groups.push(label);}show(await call('groupTab',{id:currentTab().id,group:label}));groupFilter=label;field.value='';renderLibrary();renderTabs();}); for(const [quick,original] of [['quick-ai','ai-toggle'],['quick-passwords','passwords'],['quick-inspect','inspect'],['quick-agents','agents']])wire(quick,()=>$(original).click()); wire('tools-toggle',async()=>{if(!lastState?.settings?.available||!lastState.settings.visible)throw Error('Settings are managed by your administrator.');const menu=$('tools-menu');if(menu){menu.hidden=!menu.hidden;$('tools-toggle').setAttribute('aria-expanded',String(!menu.hidden));await updateChrome();}}); for(const id of ['theme-mode','accent-mode','tab-mode'])$(id).onchange=task(async()=>{ requireSection('appearance',true); try{const settings=await call('updateSettings',{mode:$('theme-mode').value==='auto'?'system':$('theme-mode').value,accent:$('accent-mode').value,tabMode:$('tab-mode').value});lastState={...lastState,settings};} finally{applyManagedSettings();} }); window.addEventListener('resize',()=>updateChrome().catch(()=>{})); document.addEventListener('click',event=>{const menu=$('tools-menu');if(!menu||menu.hidden)return;if(event.target.closest('.menu-item')||(!event.target.closest('#tools-menu')&&!event.target.closest('#tools-toggle'))){menu.hidden=true;$('tools-toggle')?.setAttribute('aria-expanded','false');updateChrome().catch(()=>{});}}); window.addEventListener('keydown',event=>{if(event.ctrlKey&&event.key.toLowerCase()==='t'&&lastState?.ready){event.preventDefault();newTab().catch(()=>{});}if(event.key==='Escape'&&overviewOpen){overviewOpen=false;startPageOpen=!!currentTab().startPage;updateChrome().catch(()=>{});}}); applyAppearance();renderTabs();renderLibrary(); window.safetynet.onState(show);call('state').then(show).catch(e=>{$('error').textContent=e.message;}); let libraryProposal=null; function renderBookmarkManager(){ const query=$('bookmark-search').value.trim().toLowerCase(),list=$('manager-list');list.replaceChildren(); const matches=bookmarks.filter(item=>[item.title,item.url,item.folder||''].some(value=>value.toLowerCase().includes(query))); let section='';for(const item of matches){const key=`${item.managed?'Company':'Personal'} - ${item.folder||'Unfiled'}`;if(key!==section){const label=document.createElement('h3');label.className='manager-folder';label.textContent=key;list.append(label);section=key;} const row=document.createElement('div');row.className='manager-row';row.dataset.bookmarkId=item.id;const link=button('',()=>navigateTab(item.url),'manager-link'),title=document.createElement('strong'),detail=document.createElement('small');title.textContent=item.title;detail.textContent=item.url;link.append(title,detail);row.append(link); if(item.managed){const badge=document.createElement('span');badge.className='manager-managed';badge.textContent='Managed';row.append(badge);}else if(sectionEditable('library')){const actions=document.createElement('div');actions.className='manager-row-actions';if(lastState.bookmarks.allowUserEdit)actions.append(button('Edit / move',()=>openBookmarkEditor(item)));if(lastState.bookmarks.allowUserDelete)actions.append(button('Remove',async()=>{await mutateBookmark('delete',null,item.id);renderBookmarkManager();}));row.append(actions);}list.append(row);} $('manager-empty').hidden=matches.length>0;$('manager-add').disabled=!sectionEditable('library')||!lastState?.bookmarks?.allowUserAdd;$('manager-ai').disabled=!sectionEditable('library')||!lastState?.libraryAI?.suggestionsEnabled||!!lastState?.libraryAI?.busy; $('manager-policy').textContent=sectionEditable('library')?'Company bookmarks are read-only. Personal bookmarks follow company policy. AI organization shares titles and website domains with your approved model.':'Your administrator manages this library. Changes are disabled.'; } async function openBookmarkManager(){requireSection('library');$('tools-menu').hidden=true;renderBookmarkManager();$('bookmark-manager').showModal();await updateChrome();$('bookmark-search').focus();} for(const id of ['bookmark-manager-open','favorites-manage'])wire(id,openBookmarkManager); $('bookmark-search').addEventListener('input',renderBookmarkManager);$('bookmark-manager-close').onclick=()=>$('bookmark-manager').close();$('bookmark-manager').addEventListener('close',()=>updateChrome().catch(()=>{}));wire('manager-add',()=>openBookmarkEditor()); wire('manager-ai',async()=>{requireSection('library',true);libraryProposal=await call('organizeLibrary');const diff=$('manager-diff');diff.replaceChildren();for(const [index,item] of (libraryProposal.plan.bookmarks||[]).entries()){const existing=bookmarks.find(b=>b.id===item.id);if(!existing||existing.managed)throw Error('Organization proposal no longer matches your library');const row=document.createElement('p');row.textContent=`${index+1}. ${existing.title}: ${existing.folder||'Unfiled'} -> ${item.folder||'Unfiled'}`;diff.append(row);}for(const item of libraryProposal.plan.tabs||[]){const existing=tabs.find(t=>t.id===item.id);if(!existing)throw Error('Organization proposal no longer matches your tabs');const row=document.createElement('p');row.textContent=`Tab ${existing.title}: ${existing.group||'Ungrouped'} -> ${item.group||'Ungrouped'}`;diff.append(row);}$('manager-proposal').hidden=false;}); wire('manager-apply',async()=>{if(!libraryProposal)throw Error('Request a fresh organization proposal');await call('applyLibraryOrganization',{proposalId:libraryProposal.proposalId,confirm:true});libraryProposal=null;$('manager-proposal').hidden=true;show(await call('state'));renderBookmarkManager();});$('manager-discard').onclick=()=>{libraryProposal=null;$('manager-proposal').hidden=true;};