feat(automation): add macOS permission check before automation start

This commit is contained in:
2025-11-22 14:52:48 +01:00
parent 98baa1c3bc
commit c0e0e00c4c
6 changed files with 510 additions and 9 deletions

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useCallback } from 'react';
import { SessionCreationForm } from './components/SessionCreationForm';
import { SessionProgressMonitor } from './components/SessionProgressMonitor';
import type { HostedSessionConfig } from '../../../packages/domain/entities/HostedSessionConfig';
@@ -12,25 +12,72 @@ interface SessionProgress {
errorMessage: string | null;
}
interface PermissionStatus {
accessibility: boolean;
screenRecording: boolean;
platform: string;
}
export function App() {
const [sessionId, setSessionId] = useState<string | null>(null);
const [progress, setProgress] = useState<SessionProgress | null>(null);
const [isRunning, setIsRunning] = useState(false);
const [permissionStatus, setPermissionStatus] = useState<PermissionStatus | null>(null);
const [permissionChecking, setPermissionChecking] = useState(true);
const [missingPermissions, setMissingPermissions] = useState<string[]>([]);
const checkPermissions = useCallback(async () => {
if (!window.electronAPI) return;
setPermissionChecking(true);
try {
const result = await window.electronAPI.checkPermissions();
setPermissionStatus(result.status);
setMissingPermissions(result.missingPermissions);
} catch (error) {
console.error('Failed to check permissions:', error);
} finally {
setPermissionChecking(false);
}
}, []);
useEffect(() => {
// Check permissions on app start
checkPermissions();
if (window.electronAPI) {
window.electronAPI.onSessionProgress((newProgress: SessionProgress) => {
setProgress(newProgress);
if (newProgress.state === 'COMPLETED' ||
newProgress.state === 'FAILED' ||
if (newProgress.state === 'COMPLETED' ||
newProgress.state === 'FAILED' ||
newProgress.state === 'STOPPED_AT_STEP_18') {
setIsRunning(false);
}
});
}
}, []);
}, [checkPermissions]);
const handleOpenPermissionSettings = async (pane?: 'accessibility' | 'screenRecording') => {
if (!window.electronAPI) return;
await window.electronAPI.openPermissionSettings(pane);
};
const handleRequestAccessibility = async () => {
if (!window.electronAPI) return;
await window.electronAPI.requestAccessibility();
// Recheck permissions after request
setTimeout(checkPermissions, 500);
};
const handleStartAutomation = async (config: HostedSessionConfig) => {
// Recheck permissions before starting
await checkPermissions();
if (missingPermissions.length > 0) {
alert(`Cannot start automation: Missing permissions: ${missingPermissions.join(', ')}`);
return;
}
setIsRunning(true);
const result = await window.electronAPI.startAutomation(config);
@@ -38,7 +85,13 @@ export function App() {
setSessionId(result.sessionId);
} else {
setIsRunning(false);
alert(`Failed to start automation: ${result.error}`);
if (result.permissionError) {
// Update permission status
await checkPermissions();
alert(`Permission Error: ${result.error}`);
} else {
alert(`Failed to start automation: ${result.error}`);
}
}
};
@@ -54,6 +107,9 @@ export function App() {
}
};
const isMacOS = permissionStatus?.platform === 'darwin';
const hasAllPermissions = missingPermissions.length === 0;
return (
<div style={{
display: 'flex',
@@ -71,9 +127,127 @@ export function App() {
<p style={{ marginBottom: '2rem', color: '#aaa' }}>
Hosted Session Automation POC
</p>
{/* Permission Banner */}
{isMacOS && !permissionChecking && !hasAllPermissions && (
<div style={{
marginBottom: '1.5rem',
padding: '1rem',
backgroundColor: '#3d2020',
border: '1px solid #dc3545',
borderRadius: '8px',
}}>
<h3 style={{ color: '#ff6b6b', margin: '0 0 0.5rem 0', fontSize: '1rem' }}>
Missing Permissions
</h3>
<p style={{ color: '#ffaaaa', margin: '0 0 1rem 0', fontSize: '0.9rem' }}>
GridPilot requires macOS permissions to control your computer for automation.
Please grant the following permissions:
</p>
<ul style={{ color: '#ffaaaa', margin: '0 0 1rem 0', paddingLeft: '1.5rem', fontSize: '0.9rem' }}>
{!permissionStatus?.accessibility && (
<li style={{ marginBottom: '0.5rem' }}>
<strong>Accessibility:</strong> Required for keyboard and mouse control
</li>
)}
{!permissionStatus?.screenRecording && (
<li style={{ marginBottom: '0.5rem' }}>
<strong>Screen Recording:</strong> Required for screen capture and window detection
</li>
)}
</ul>
<div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap' }}>
{!permissionStatus?.accessibility && (
<button
onClick={handleRequestAccessibility}
style={{
padding: '0.5rem 1rem',
backgroundColor: '#007bff',
color: '#fff',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
fontSize: '0.9rem',
}}
>
Request Accessibility
</button>
)}
{!permissionStatus?.accessibility && (
<button
onClick={() => handleOpenPermissionSettings('accessibility')}
style={{
padding: '0.5rem 1rem',
backgroundColor: '#6c757d',
color: '#fff',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
fontSize: '0.9rem',
}}
>
Open Accessibility Settings
</button>
)}
{!permissionStatus?.screenRecording && (
<button
onClick={() => handleOpenPermissionSettings('screenRecording')}
style={{
padding: '0.5rem 1rem',
backgroundColor: '#6c757d',
color: '#fff',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
fontSize: '0.9rem',
}}
>
Open Screen Recording Settings
</button>
)}
<button
onClick={checkPermissions}
style={{
padding: '0.5rem 1rem',
backgroundColor: '#28a745',
color: '#fff',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
fontSize: '0.9rem',
}}
>
Recheck Permissions
</button>
</div>
<p style={{ color: '#888', margin: '1rem 0 0 0', fontSize: '0.8rem' }}>
After granting permissions in System Preferences, click "Recheck Permissions" or restart the app.
</p>
</div>
)}
{/* Permission Status Indicator */}
{isMacOS && !permissionChecking && hasAllPermissions && (
<div style={{
marginBottom: '1.5rem',
padding: '0.75rem 1rem',
backgroundColor: '#1e3d1e',
border: '1px solid #28a745',
borderRadius: '8px',
display: 'flex',
alignItems: 'center',
gap: '0.5rem',
}}>
<span style={{ color: '#28a745', fontSize: '1.2rem' }}></span>
<span style={{ color: '#8eff8e', fontSize: '0.9rem' }}>
All permissions granted - Ready for automation
</span>
</div>
)}
<SessionCreationForm
onSubmit={handleStartAutomation}
disabled={isRunning}
disabled={isRunning || (isMacOS && !hasAllPermissions)}
/>
{isRunning && (
<button