Implement managed browser security, individual agents and observability
This commit is contained in:
1 parent
ff482ebe23
commit
ea448de2d4
172 files changed
+5404
-347
No files matched your search
@@ -0,0 +1,27 @@
|
||||
'use strict';
|
||||
// Native history only: pages and agents cannot supply a navigation target.
|
||||
async function performHistory({contents,action,allowed,current,signal,timeoutMs=10000}={}) {
|
||||
if(!contents||!['back','forward','reload'].includes(action)||typeof allowed!=='function'||typeof current!=='function'||!Number.isFinite(timeoutMs)||timeoutMs<1||timeoutMs>60000)throw Error('Invalid history operation');
|
||||
const guard=async(phase='before')=>{if(signal?.aborted)throw Error('History operation cancelled');if(contents.isDestroyed?.())throw Error('History contents unavailable');if(await current(phase)!==true)throw Error('History context changed');if(signal?.aborted)throw Error('History operation cancelled');};
|
||||
const target=()=>action==='reload'?contents.getURL():contents.navigationHistory.getEntryAtIndex(contents.navigationHistory.getActiveIndex()+(action==='back'?-1:1))?.url;
|
||||
await guard();const url=target();
|
||||
if(!url)return {completed:false,url:contents.getURL()};
|
||||
let parsed;try{parsed=new URL(url);}catch{throw Error('Invalid history destination');}
|
||||
if(!['http:','https:'].includes(parsed.protocol)||parsed.username||parsed.password||!await allowed(url))throw Error('History destination denied');
|
||||
await guard();if(target()!==url)throw Error('History destination changed');
|
||||
return new Promise((resolve,reject)=>{
|
||||
let settled=false,dispatched=false,checking=false;
|
||||
const cleanup=()=>{clearTimeout(timer);signal?.removeEventListener('abort',abort);for(const [name,fn] of listeners)contents.removeListener(name,fn);};
|
||||
const fail=error=>{if(settled)return;settled=true;cleanup();if(dispatched&&!contents.isDestroyed?.())try{contents.stop();}catch{}reject(error);};
|
||||
const complete=async()=>{if(!dispatched||settled||checking)return;checking=true;try{await guard('after');if(contents.getURL()!==url)throw Error('History destination changed');if(!await allowed(url))throw Error('History destination denied');await guard('after');if(contents.getURL()!==url)throw Error('History destination changed');if(settled)return;settled=true;cleanup();resolve({completed:true,url});}catch(error){fail(error);}finally{checking=false;}};
|
||||
const inPage=(_event,eventUrl,isMainFrame)=>{if(isMainFrame===true){if(eventUrl!==url)fail(Error('History destination changed'));else void complete();}};
|
||||
const loadFailed=(_event,_code,_description,_url,isMainFrame)=>{if(isMainFrame===true)fail(Error('History navigation failed'));};
|
||||
const abort=()=>fail(Error('History operation cancelled'));
|
||||
const listeners=[['did-finish-load',complete],['did-navigate-in-page',inPage],['did-fail-load',loadFailed],['destroyed',()=>fail(Error('History contents unavailable'))]];
|
||||
const timer=setTimeout(()=>fail(Error('History operation timed out')),timeoutMs);
|
||||
for(const [name,fn] of listeners)contents.on(name,fn);signal?.addEventListener('abort',abort,{once:true});
|
||||
if(signal?.aborted){abort();return;}
|
||||
try{dispatched=true;if(action==='reload')contents.reload();else contents.navigationHistory.goToIndex(contents.navigationHistory.getActiveIndex()+(action==='back'?-1:1));}catch(error){fail(error);}
|
||||
});
|
||||
}
|
||||
module.exports={performHistory};
|
||||
Reference in new issue
Block a user