51 lines
3.5 KiB
JavaScript
51 lines
3.5 KiB
JavaScript
'use strict';
|
|||
|
|
// Only the managed application window is exposed. Screen positions, titles,
|
||
|
|
// arbitrary native handles and executable arguments never cross this boundary.
|
||
|
|
const commands = Object.freeze(['minimize','maximize','restore','focus']);
|
||
|
|
function createWindowControls({getWindow,getContext,isCurrent,authorize=()=>true,traceOperation=(_name,fn)=>fn()}={}) {
|
||
|
|
if([getWindow,getContext,isCurrent,authorize,traceOperation].some(fn=>typeof fn!=='function'))throw TypeError('Window callbacks are required');
|
||
|
|
const snapshot=(action,signal)=>{
|
||
|
|
const context=getContext(),window=getWindow(),policy=context?.policy;
|
||
|
|
if(signal?.aborted||context?.ready!==true||policy?.agents?.enabled!==true||!Array.isArray(policy.agents.scopes)||!policy.agents.scopes.includes(action))throw Error('Agent window scope denied');
|
||
|
|
if(policy.expiresAt&&(!Number.isFinite(Date.parse(policy.expiresAt))||Date.parse(policy.expiresAt)<=Date.now()))throw Error('Managed policy expired');
|
||
|
|
if(!window||window.isDestroyed()||!window.webContents||window.webContents.isDestroyed())throw Error('Managed window unavailable');
|
||
|
|
return {context,window,contents:window.webContents,policy,version:context.policyVersion??policy.version};
|
||
|
|
};
|
||
|
|
const check=(initial,action,signal)=>{
|
||
|
|
const latest=snapshot(action,signal);
|
||
|
|
if(latest.window!==initial.window||latest.contents!==initial.contents||latest.policy!==initial.policy||latest.version!==initial.version||isCurrent(initial.context)!==true)throw Error('Managed window changed during action');
|
||
|
|
};
|
||
|
|
const approved=async(initial,action,signal)=>{
|
||
|
|
check(initial,action,signal);
|
||
|
|
if(await authorize(action,initial.context)!==true)throw Error('Agent window authorization denied');
|
||
|
|
check(initial,action,signal);
|
||
|
|
};
|
||
|
|
const project=window=>{
|
||
|
|
const bounds=window.getBounds();
|
||
|
|
if(!bounds||!['width','height'].every(key=>Number.isSafeInteger(bounds[key])&&bounds[key]>=0&&bounds[key]<=65535))throw Error('Invalid managed window dimensions');
|
||
|
|
const result={width:bounds.width,height:bounds.height};
|
||
|
|
for(const [key,method] of [['visible','isVisible'],['focused','isFocused'],['minimized','isMinimized'],['maximized','isMaximized'],['fullScreen','isFullScreen']]){
|
||
|
|
const value=window[method]();if(typeof value!=='boolean')throw Error('Invalid managed window state');result[key]=value;
|
||
|
|
}
|
||
|
|
return result;
|
||
|
|
};
|
||
|
|
const run=async(action,args,options={})=>{
|
||
|
|
if(!args||Object.getPrototypeOf(args)!==Object.prototype||Object.keys(args).some(key=>action==='window:control'?key!=='command':true)||action==='window:control'&&!commands.includes(args.command))throw Error('Invalid window action');
|
||
|
|
const {signal}=options,initial=snapshot(action,signal);
|
||
|
|
await approved(initial,action,signal);
|
||
|
|
return traceOperation(action,async()=>{
|
||
|
|
await approved(initial,action,signal);
|
||
|
|
if(action==='window:control'){
|
||
|
|
// Electron operations are synchronous. Do not retry an effect after a
|
||
|
|
// subsequent cancellation, policy change or diagnostic delivery error.
|
||
|
|
try{initial.window[args.command]();}catch{throw Error('Managed window operation failed');}
|
||
|
|
await Promise.resolve();
|
||
|
|
}
|
||
|
|
await approved(initial,action,signal);
|
||
|
|
const state=project(initial.window);check(initial,action,signal);
|
||
|
|
return action==='window:control'?{completed:true,state}:state;
|
||
|
|
});
|
||
|
|
};
|
||
|
|
return {state:(args={},options)=>run('window:state',args,options),control:(args,options)=>run('window:control',args,options)};
|
||
|
|
}
|
||
|
|
module.exports={createWindowControls};
|