docs(electron): fix cross-repo confusion, remove misplaced file, update integration docs
- Remove tmp_shell_handlers.ts from SvelteKit repo — this was a draft of the
Electron main-process shell handler that was placed in the wrong repo. It used
ipcMain (Electron main-process only) and could never run in SvelteKit. Was not
imported anywhere but was misleading.
- Fix src/lib/electron/README.md:
- Correct broken doc link (was AE_EVENTS_LAUNCHER_NATIVE_INTEGRATION.md,
actual filename is PROJECT__AE_Events_Launcher_Native_integration.md)
- Mark electron_native.js as DEPRECATED — do not import (was described as
active "Bridge Logic", which was incorrect and confusion-causing)
- Add clear bridge architecture diagram showing the three-layer flow
- Note that aether_app_native_electron/ is the active Electron repo
- Update PROJECT__AE_Events_Launcher_Native_integration.md:
- Fix Layer 1 file path: aether_app_native/ → aether_app_native_electron/
- Section 5.3: Phase 5 actuators are all implemented, not "planned" — update
recording, display layout, power control, window control, wallpaper to reflect
actual implementation status; note update_app is a stub
- Section 7: Expand IPC whitelist to cover all relay functions including new
cleanup_tmp_files, system handlers, and full parameter signatures
- Document get_seed_config / get_jwt as intentionally not relayed to UI
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
# Aether Events Launcher: Native Electron Integration
|
||||
|
||||
> **Status:** Operational / Phase 5 Implementation
|
||||
> **Last Updated:** February 10, 2026
|
||||
> **Primary Platform:** macOS (Darwin)
|
||||
> **Status:** Operational / Phase 5 Implementation
|
||||
> **Last Updated:** 2026-03-11
|
||||
> **Primary Platform:** macOS (Darwin)
|
||||
> **Fallback Platform:** Linux / Windows
|
||||
|
||||
## 1. Overview
|
||||
@@ -22,7 +22,8 @@ The Aether Events Launcher utilizes an Electron-based "Native Shell" to provide
|
||||
The integration is built on a decoupled three-layer communication model to ensure security and cross-platform flexibility.
|
||||
|
||||
### 2.1 Layer 1: The Engine (Main Process)
|
||||
- **File:** `aether_app_native/src/main/*.ts`
|
||||
- **Repo:** `~/OSIT_dev/aether_app_native_electron/` (separate git repo)
|
||||
- **File:** `aether_app_native_electron/src/main/*.ts`
|
||||
- **Role:** Performs the heavy lifting (Filesystem, Shell, AppleScript).
|
||||
- **Responsibilities:**
|
||||
- Managing the **Hashed Cache** directory.
|
||||
@@ -94,9 +95,14 @@ The native shell provides specialized handlers for controlling the "Podium Exper
|
||||
- **Telemetry:** Pushes `cpu_usage`, `memory_free_gb`, and `foreground_app` via heartbeats using the `get_device_info` relay.
|
||||
- **Self-Update (Roadmap):** Plan to monitor Syncthing `admin_share` for newer `.app` versions and perform atomic swaps.
|
||||
|
||||
### 5.3 Planned Actuators (Future Phases)
|
||||
- **Recording:** Control for `aperture` session capture (`start`, `stop`).
|
||||
- **Display Layouts:** Toggling between Mirror and Extended modes via `displayplacer`.
|
||||
### 5.3 Implemented Actuators (Phase 5 Complete)
|
||||
- **Recording:** `manage_recording({action})` — Aperture session capture (`start`, `stop`, `status`). macOS only.
|
||||
- **Display Layouts:** `set_display_layout({mode})` — Mirror / Extend via `displayplacer`. macOS only.
|
||||
- **Power Control:** `power_control({action})` — Shutdown, reboot, sleep. macOS + Linux.
|
||||
- **Window Control:** `window_control({action})` — Maximize, minimize, fullscreen, kiosk mode.
|
||||
- **Wallpaper:** `set_wallpaper({path})` — macOS (AppleScript) + Linux (gsettings).
|
||||
|
||||
> **Note:** `update_app` is implemented as a stub — downloads but does not install. Not yet functional for end users.
|
||||
|
||||
---
|
||||
|
||||
@@ -128,19 +134,47 @@ The UI dynamically filters fields based on the user's focus. Enabling Technical
|
||||
|
||||
## 7. Implementation Reference (IPC Whitelist)
|
||||
|
||||
### Core Functions (`electron_relay.ts`)
|
||||
- `get_device_config()`: Returns hydrated device settings from the native shell.
|
||||
- `get_device_info()`: Returns OS metadata, IP list, and path placeholders (`[home]`, `[tmp]`).
|
||||
- `check_hash_file_cache({cache_root, hash})`: Local filesystem hash check.
|
||||
- `download_to_cache({url, cache_root, hash, ...})`: Authorized background download to the hashed cache.
|
||||
- `launch_from_cache({cache_root, hash, temp_root, filename})`: Atomic "Safe Handover" trigger.
|
||||
- `launch_presentation({path, app})`: Phase 5 specialized launcher with slideshow activation.
|
||||
- `control_presentation({app, action})`: Remote navigation (next/prev) for active decks.
|
||||
- `kill_processes({process_name_li})`: Graceful application termination.
|
||||
- `manage_recording({action})`: Presentation session capture control (Aperture).
|
||||
- `list_tools()`: Manifest of all available native bridge functions.
|
||||
All functions below are exported from `src/lib/electron/electron_relay.ts` and safely
|
||||
no-op when `window.aetherNative` is not present (i.e., in browser/non-native mode).
|
||||
|
||||
### Config & Info
|
||||
- `get_device_config()` — Returns hydrated device settings injected by the native shell on startup.
|
||||
- `get_device_info()` — Returns OS metadata, IP list, hostname, and path placeholders (`[home]`, `[tmp]`).
|
||||
|
||||
### File Cache
|
||||
- `check_hash_file_cache({cache_root, hash, hash_prefix_length?})` — Verifies a file exists in the local hashed cache.
|
||||
- `download_to_cache({url, cache_root, hash, api_key, account_id, hash_prefix_length?})` — Streams a file download to the hashed cache with SHA-256 integrity check.
|
||||
- `launch_from_cache({cache_root, hash, temp_root, filename, hash_prefix_length?})` — Atomic "Safe Handover": copy from cache → tmp → rename → execute.
|
||||
- `cleanup_tmp_files({cache_root, max_age_minutes?})` — Removes stale `*.tmp` download artifacts. Default: 1440 min (24h). Called at launcher startup.
|
||||
|
||||
> `hash_prefix_length` defaults to `2` throughout. Do not change without coordinating all devices — mismatched values create orphaned cache subdirectories.
|
||||
|
||||
### Shell & OS
|
||||
- `open_folder(path)` — Opens a path in the OS file manager.
|
||||
- `run_cmd({cmd, timeout?, return_stdout?})` — Async shell command execution.
|
||||
- `run_cmd_sync({cmd, return_stdout?})` — Synchronous shell command execution.
|
||||
- `run_osascript(script)` — Executes an AppleScript string. macOS only.
|
||||
- `kill_processes({process_name_li})` — Gracefully terminates processes by name.
|
||||
- `open_local_file_v2(path)` — Opens a file with its default OS application.
|
||||
|
||||
### Presentations (Phase 5)
|
||||
- `launch_presentation({path, app?, os?})` — Platform-aware launcher. macOS: PowerPoint/Keynote via AppleScript. Linux: LibreOffice Impress. Resolves `[home]`/`[tmp]` placeholders.
|
||||
- `control_presentation({app, action})` — Slide navigation (`next`/`prev`/`start`/`stop`) for PowerPoint or Keynote via AppleScript.
|
||||
|
||||
### System Management (Phase 5)
|
||||
- `set_wallpaper({path})` — Sets desktop wallpaper. macOS (AppleScript) + Linux (gsettings).
|
||||
- `window_control({action, value?})` — Electron window management: maximize, minimize, fullscreen, kiosk.
|
||||
- `set_display_layout({mode, configStr?})` — Mirror or extend displays via displayplacer. macOS only.
|
||||
- `power_control({action})` — Shutdown, reboot, or sleep the host machine. macOS + Linux.
|
||||
- `manage_recording({action, options?})` — Aperture capture control (`start`/`stop`/`status`). macOS only.
|
||||
- `open_external({url, app?})` — Opens a URL in Chrome, Firefox, or the default browser.
|
||||
- `update_app(args)` — **Stub only.** Downloads but does not install. Not yet functional.
|
||||
- `list_tools()` — Returns a self-documenting manifest of all available native bridge functions.
|
||||
|
||||
### Path Placeholders
|
||||
To ensure cross-platform compatibility, all paths should use:
|
||||
- `[home]`: User home directory.
|
||||
- `[tmp]`: System temporary directory.
|
||||
All paths passed to native handlers should use tokens rather than hardcoded OS paths:
|
||||
- `[home]` — Resolved to the user's home directory by the native bridge.
|
||||
- `[tmp]` — Resolved to the system temporary directory.
|
||||
|
||||
### Not Exposed via Relay (intentional)
|
||||
- `get_seed_config` / `get_jwt` — Exposed in the preload but not relayed to the UI. The JWT and seed are injected into the environment at startup; components should not call these directly.
|
||||
|
||||
@@ -1,34 +1,50 @@
|
||||
# Aether Native Integration (Electron)
|
||||
|
||||
This directory contains the SvelteKit-side bridge for the Aether Native App (Electron). It provides the UI with access to OS-level capabilities required for the **Event Launcher**.
|
||||
This directory contains the SvelteKit-side bridge for the Aether Native App (Electron).
|
||||
It provides the UI with access to OS-level capabilities required for the **Event Launcher**.
|
||||
|
||||
## 📖 Technical Manual
|
||||
For detailed architecture, lifecycle, and IPC specifications, see:
|
||||
👉 **[AE_EVENTS_LAUNCHER_NATIVE_INTEGRATION.md](../../../documentation/AE_EVENTS_LAUNCHER_NATIVE_INTEGRATION.md)**
|
||||
The native implementation lives in a separate repo:
|
||||
`~/OSIT_dev/aether_app_native_electron/`
|
||||
|
||||
## 📂 File Manifest
|
||||
## Technical Manual
|
||||
|
||||
For detailed architecture, lifecycle, and IPC specifications, see:
|
||||
[PROJECT__AE_Events_Launcher_Native_integration.md](../../../documentation/PROJECT__AE_Events_Launcher_Native_integration.md)
|
||||
|
||||
## File Manifest
|
||||
|
||||
| File | Role | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| `electron_relay.ts` | **Messenger** | The TypeScript API used by Svelte components. Standardizes calls to `snake_case`. |
|
||||
| `electron_native.js` | **Bridge Logic** | (Internal) Logic often used in the Preload or Renderer to facilitate IPC. |
|
||||
| `electron_relay.ts` | **Active — Messenger** | The TypeScript API used by Svelte components. Standardizes all IPC calls to `snake_case`. This is the only file in this directory that should be imported. |
|
||||
| `electron_native.js` | **DEPRECATED — Do not import** | Legacy V2/V4 reference file. The active native logic lives in `aether_app_native_electron/`. Kept for historical reference only. |
|
||||
|
||||
## 🚀 Usage Example
|
||||
## Usage Example
|
||||
|
||||
```typescript
|
||||
import { is_native, launch_from_cache } from '$lib/electron/electron_relay';
|
||||
|
||||
if (is_native) {
|
||||
await launch_from_cache({
|
||||
cache_root: $ae_loc.native_device.local_file_cache_path,
|
||||
cache_root: $ae_loc.local_file_cache_path,
|
||||
hash: file_obj.hash_sha256,
|
||||
temp_root: $ae_loc.native_device.host_file_temp_path,
|
||||
temp_root: $ae_loc.host_file_temp_path,
|
||||
filename: file_obj.filename
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## 🔐 Security Standards
|
||||
- **Namespace:** All native functions are exposed via `window.aetherNative`.
|
||||
- **Whitelisting:** Only specific intents (e.g., `launch_presentation`) are allowed.
|
||||
- **Path Isolation:** All file operations are restricted to `[home]` and `[tmp]` via placeholder resolution.
|
||||
## Security Model
|
||||
|
||||
- All native functions are accessed via `window.aetherNative` (contextBridge)
|
||||
- Only explicitly whitelisted IPC channels are exposed — no arbitrary code execution
|
||||
- Path isolation: all file operations use `[home]` / `[tmp]` placeholders resolved by the native bridge
|
||||
- Context isolation is enabled; Node integration is disabled in the renderer
|
||||
|
||||
## Bridge Architecture (Three Layers)
|
||||
|
||||
```text
|
||||
Svelte Component
|
||||
└── electron_relay.ts (this directory — typed wrappers, snake_case API)
|
||||
└── window.aetherNative (contextBridge whitelist in preload/index.ts)
|
||||
└── ipcMain handlers (aether_app_native_electron/src/main/*.ts)
|
||||
```
|
||||
|
||||
@@ -1,212 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import { ipcMain, shell } from 'electron';
|
||||
import { exec, execSync } from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import { expandPath } from './file_utils';
|
||||
|
||||
export function registerShellHandlers() {
|
||||
ipcMain.handle('native:open-folder', async (event, folderPath: string) => {
|
||||
const cleanPath = expandPath(folderPath);
|
||||
const error = await shell.openPath(cleanPath);
|
||||
return { success: !error, error };
|
||||
});
|
||||
|
||||
ipcMain.handle('native:run-cmd', async (event, { cmd, timeout = 30000 }) => {
|
||||
const cleanCmd = expandPath(cmd);
|
||||
return new Promise((resolve) => {
|
||||
exec(cleanCmd, { timeout }, (error, stdout, stderr) => {
|
||||
resolve({ success: !error, stdout: stdout.trim(), stderr: stderr.trim(), error: error ? error.message : null });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.handle('native:run-cmd-sync', async (event, { cmd }) => {
|
||||
const cleanCmd = expandPath(cmd);
|
||||
try {
|
||||
const stdout = execSync(cleanCmd).toString();
|
||||
return { success: true, stdout: stdout.trim() };
|
||||
} catch (error: any) {
|
||||
return { success: false, error: error.message, stderr: error.stderr?.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('native:run-osascript', async (event, script: string) => {
|
||||
if (os.platform() !== 'darwin') return { success: false, error: 'AppleScript is only available on macOS' };
|
||||
const escapedScript = script.replace(/"/g, '\"');
|
||||
const cmd = `osascript -e "${escapedScript}"`;
|
||||
return new Promise((resolve) => {
|
||||
exec(cmd, (error, stdout, stderr) => {
|
||||
resolve({ success: !error, stdout: stdout.trim(), stderr: stderr.trim(), error: error ? error.message : null });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.handle('native:kill-processes', async (event, { process_name_li = [] }) => {
|
||||
console.log(`Native: Killing processes -> `, process_name_li);
|
||||
const results = [];
|
||||
for (const name of process_name_li) {
|
||||
const cmd = os.platform() === 'win32'
|
||||
? `taskkill /F /IM ${name} /T`
|
||||
: `pkill -f ${name}`;
|
||||
try {
|
||||
execSync(cmd);
|
||||
results.push({ name, success: true });
|
||||
} catch (e: any) {
|
||||
results.push({ name, success: false, error: e.message });
|
||||
}
|
||||
}
|
||||
return { success: true, results };
|
||||
});
|
||||
|
||||
ipcMain.handle('native:open-local-file-v2', async (event, filePath: string) => {
|
||||
const cleanPath = expandPath(filePath);
|
||||
const error = await shell.openPath(cleanPath);
|
||||
return { success: !error, error };
|
||||
});
|
||||
|
||||
ipcMain.handle('native:launch-presentation', async (event, { path: rawPath, app: appType = 'default' }) => {
|
||||
const cleanedPath = expandPath(rawPath);
|
||||
console.log(`Native: Launching Presentation -> ${cleanedPath} (App: ${appType})`);
|
||||
|
||||
if (os.platform() === 'linux') {
|
||||
const cmd = `libreoffice --impress "${cleanedPath}"`;
|
||||
return new Promise((resolve) => {
|
||||
exec(cmd, (err, stdout, stderr) => {
|
||||
if (err) resolve({ success: false, error: err.message });
|
||||
else resolve({ success: true, stdout, stderr });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (os.platform() === 'darwin') {
|
||||
let script = '';
|
||||
if (appType === 'keynote') {
|
||||
script = `
|
||||
tell application "Keynote"
|
||||
activate
|
||||
open (POSIX file "${cleanedPath}")
|
||||
delay 1
|
||||
start (front document)
|
||||
end tell
|
||||
`;
|
||||
} else if (appType === 'powerpoint') {
|
||||
script = `
|
||||
tell application "Microsoft PowerPoint"
|
||||
activate
|
||||
open (POSIX file "${cleanedPath}")
|
||||
delay 1
|
||||
run slide show of active presentation
|
||||
end tell
|
||||
`;
|
||||
}
|
||||
|
||||
if (script) {
|
||||
return new Promise((resolve) => {
|
||||
const escapedScript = script.replace(/"/g, '\\"');
|
||||
exec(`osascript -e "${escapedScript}"`, (err, stdout, stderr) => {
|
||||
if (err) resolve({ success: false, error: err.message });
|
||||
else resolve({ success: true });
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const error = await shell.openPath(cleanedPath);
|
||||
return { success: !error, error };
|
||||
});
|
||||
|
||||
ipcMain.handle('native:control-presentation', async (event, { app, action }) => {
|
||||
if (os.platform() !== 'darwin') return { success: false, error: 'Presentation control is only available on macOS' };
|
||||
|
||||
let script = '';
|
||||
if (app === 'powerpoint') {
|
||||
switch (action) {
|
||||
case 'next': script = 'tell application "Microsoft PowerPoint" to next slide of slide show view of active presentation'; break;
|
||||
case 'prev': script = 'tell application "Microsoft PowerPoint" to previous slide of slide show view of active presentation'; break;
|
||||
case 'start': script = 'tell application "Microsoft PowerPoint" to run slide show of active presentation'; break;
|
||||
case 'stop': script = 'tell application "Microsoft PowerPoint" to stop slide show of active presentation'; break;
|
||||
}
|
||||
} else if (app === 'keynote') {
|
||||
switch (action) {
|
||||
case 'next': script = 'tell application "Keynote" to show next'; break;
|
||||
case 'prev': script = 'tell application "Keynote" to show previous'; break;
|
||||
case 'start': script = 'tell application "Keynote" to start (front document)'; break;
|
||||
case 'stop': script = 'tell application "Keynote" to stop'; break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!script) return { success: false, error: `Unsupported app or action: ${app}/${action}` };
|
||||
|
||||
return new Promise((resolve) => {
|
||||
exec(`osascript -e "${script.replace(/"/g, '\\"')}"`, (error, stdout, stderr) => {
|
||||
resolve({ success: !error, stdout: stdout.trim(), stderr: stderr.trim(), error: error ? error.message : null });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.handle('native:list-tools', async () => {
|
||||
return [
|
||||
{
|
||||
name: 'open_folder',
|
||||
description: 'Opens a directory in the OS file explorer (Finder/Files/Explorer).',
|
||||
params: { path: 'string' }
|
||||
},
|
||||
{
|
||||
name: 'run_cmd',
|
||||
description: 'Executes an asynchronous shell command with a timeout.',
|
||||
params: { cmd: 'string', timeout: 'number (optional)' }
|
||||
},
|
||||
{
|
||||
name: 'run_cmd_sync',
|
||||
description: 'Executes a synchronous shell command.',
|
||||
params: { cmd: 'string' }
|
||||
},
|
||||
{
|
||||
name: 'run_osascript',
|
||||
description: 'Executes a raw AppleScript string (macOS only).',
|
||||
params: { script: 'string' }
|
||||
},
|
||||
{
|
||||
name: 'kill_processes',
|
||||
description: 'Forcefully terminates processes by name.',
|
||||
params: { process_name_li: 'string[]' }
|
||||
},
|
||||
{
|
||||
name: 'open_local_file_v2',
|
||||
description: 'Opens a local file using the default OS handler.',
|
||||
params: { filePath: 'string' }
|
||||
},
|
||||
{
|
||||
name: 'launch_presentation',
|
||||
description: 'Phase 5: Specialized launcher for PowerPoint, Keynote, and LibreOffice with auto-focus.',
|
||||
params: { path: 'string', app: 'default|powerpoint|keynote' }
|
||||
},
|
||||
{
|
||||
name: 'control_presentation',
|
||||
description: 'Phase 5: Remote navigation for active slideshows.',
|
||||
params: { app: 'powerpoint|keynote', action: 'next|prev|start|stop' }
|
||||
},
|
||||
{
|
||||
name: 'check_cache',
|
||||
description: 'Checks if a file exists in the local organized cache.',
|
||||
params: { cache_root: 'string', hash: 'string', hash_prefix_length: 'number' }
|
||||
},
|
||||
{
|
||||
name: 'download_to_cache',
|
||||
description: 'Downloads a file from the API directly into the native cache.',
|
||||
params: { url: 'string', cache_root: 'string', hash: 'string', api_key: 'string', account_id: 'string' }
|
||||
},
|
||||
{
|
||||
name: 'launch_from_cache',
|
||||
description: 'Atomic operation: Copies file from cache to temp with original name and launches via specialized handler.',
|
||||
params: { cache_root: 'string', hash: 'string', temp_root: 'string', filename: 'string' }
|
||||
},
|
||||
{
|
||||
name: 'get_device_info',
|
||||
description: 'Returns hardware and OS metadata (CPUs, RAM, IP addresses, Hostname).',
|
||||
params: {}
|
||||
}
|
||||
];
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user