feat(launcher): configurable launch scripts + composable native primitives
- electron_relay: type launch_from_cache with script_template param; add copy_from_cache_to_temp export; add JSDoc for run_osascript hardening - launcher_file_cont: add get_launch_script_template() helper reading from device-level (event_device.data_json.launch_scripts) and event-level (events_loc.launcher.launch_scripts) config; wire into handle_open_file() - PROJECT__AE_Events_Launcher_Native_integration.md: add Section 8 (Configurable Launch Scripts); update IPC reference for new/changed handlers - MODULE__AE_Events_PressMgmt_Launcher.md: add configurable launch behavior note to Native Mode Safe Handover section
This commit is contained in:
@@ -64,11 +64,68 @@ export async function launch_from_cache({
|
||||
hash,
|
||||
temp_root,
|
||||
filename,
|
||||
hash_prefix_length = 2
|
||||
}: any) {
|
||||
hash_prefix_length = 2,
|
||||
script_template = null
|
||||
}: {
|
||||
cache_root: string;
|
||||
hash: string;
|
||||
temp_root: string;
|
||||
filename: string;
|
||||
hash_prefix_length?: number;
|
||||
/**
|
||||
* Optional data-driven launch script. If provided, Electron runs this instead of
|
||||
* its hardcoded extension-based logic — no app rebuild needed for script changes.
|
||||
*
|
||||
* Two formats:
|
||||
* - AppleScript: multi-line string with {{path}} placeholder (macOS only)
|
||||
* - Shell command: prefix with "shell:" → e.g. "shell:open \"{{path}}\""
|
||||
*
|
||||
* Configure via event_device.data_json.launch_scripts or $events_loc.launcher.launch_scripts.
|
||||
* If null, Electron falls through to its built-in hardcoded defaults.
|
||||
*/
|
||||
script_template?: string | null;
|
||||
}) {
|
||||
if (!native)
|
||||
return { success: false, error: 'Native bridge not available' };
|
||||
return await native.launch_from_cache({
|
||||
cache_root,
|
||||
hash,
|
||||
temp_root,
|
||||
filename,
|
||||
hash_prefix_length,
|
||||
script_template
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Thin cache primitive — copies a cached file to the temp directory and returns
|
||||
* the resolved path. The caller decides what happens next.
|
||||
*
|
||||
* Preferred building block for composable launch flows on the Svelte side:
|
||||
* 1. copy_from_cache_to_temp(...) → { path }
|
||||
* 2. run_osascript(template.replace('{{path}}', path))
|
||||
* OR run_cmd(`open "${path}"`)
|
||||
* OR whatever you need
|
||||
*
|
||||
* Use launch_from_cache when the built-in hardcoded logic is sufficient.
|
||||
* Use this when you want full control over what happens after the file lands in temp.
|
||||
*/
|
||||
export async function copy_from_cache_to_temp({
|
||||
cache_root,
|
||||
hash,
|
||||
temp_root,
|
||||
filename,
|
||||
hash_prefix_length = 2
|
||||
}: {
|
||||
cache_root: string;
|
||||
hash: string;
|
||||
temp_root: string;
|
||||
filename: string;
|
||||
hash_prefix_length?: number;
|
||||
}): Promise<{ success: boolean; path?: string; error?: string }> {
|
||||
if (!native)
|
||||
return { success: false, error: 'Native bridge not available' };
|
||||
return await native.copy_from_cache_to_temp({
|
||||
cache_root,
|
||||
hash,
|
||||
temp_root,
|
||||
@@ -129,6 +186,18 @@ export async function cleanup_tmp_files({
|
||||
return await native.run_cmd({ cmd, timeout: 30000, return_stdout: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes an AppleScript string. macOS only.
|
||||
*
|
||||
* HARDENED (2026-05-11): The Electron handler now writes the script to a temp .scpt
|
||||
* file and runs `osascript "/path/to/file.scpt"` rather than passing it inline via
|
||||
* the -e flag. This means:
|
||||
* - Multi-line scripts work correctly
|
||||
* - Paths with spaces or special characters work correctly
|
||||
* - No shell escaping required in the script string you pass here
|
||||
*
|
||||
* The .scpt file is deleted immediately after execution.
|
||||
*/
|
||||
export async function run_osascript(script: string) {
|
||||
if (!native)
|
||||
return { success: false, error: 'Native bridge not available' };
|
||||
|
||||
@@ -88,6 +88,41 @@ let open_file_status_message: null | string = $state(null);
|
||||
|
||||
let screen_saver_exts = ['jpg', 'png', 'PNG', 'webp'];
|
||||
|
||||
/**
|
||||
* Resolves a data-driven launch script template for a given file extension.
|
||||
* Checked in priority order:
|
||||
* 1. event_device.data_json.launch_scripts (API-driven, per-device, most specific)
|
||||
* 2. $events_loc.launcher.launch_scripts (local persistent override)
|
||||
* Keys are lowercase extensions without the dot ("pptx", "key", "pdf", etc.).
|
||||
* A "default" key acts as a catch-all for unrecognised extensions.
|
||||
*
|
||||
* Returns null when no config is found → Electron falls back to its hardcoded defaults.
|
||||
*
|
||||
* Template formats:
|
||||
* - AppleScript (macOS): plain string with {{path}} placeholder
|
||||
* - Shell command: prefix with "shell:" → "shell:open \"{{path}}\""
|
||||
*/
|
||||
function get_launch_script_template(extension: string): string | null {
|
||||
const ext = (extension || '').toLowerCase().replace('.', '');
|
||||
|
||||
// 1. Device-level config (from API, per device — highest priority)
|
||||
const device_scripts = ($ae_loc as any).native_device?.launch_scripts;
|
||||
if (device_scripts) {
|
||||
if (device_scripts[ext]) return device_scripts[ext];
|
||||
if (device_scripts['default']) return device_scripts['default'];
|
||||
}
|
||||
|
||||
// 2. Launcher local config override (set manually via Launcher config UI)
|
||||
const local_scripts = ($events_loc as any).launcher?.launch_scripts;
|
||||
if (local_scripts) {
|
||||
if (local_scripts[ext]) return local_scripts[ext];
|
||||
if (local_scripts['default']) return local_scripts['default'];
|
||||
}
|
||||
|
||||
// 3. No override — let Electron use its built-in hardcoded defaults
|
||||
return null;
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (screen_saver_exts.includes(event_file_obj.extension)) {
|
||||
if (!$events_loc.launcher.screen_saver_img_kv)
|
||||
@@ -149,11 +184,14 @@ async function handle_open_file() {
|
||||
// Phase 2/5: Use the atomic copy-and-launch operation.
|
||||
// The main process handler (file_handlers.ts) now handles the
|
||||
// specialized LibreOffice/AppleScript logic internally after copying.
|
||||
// script_template is null when no device/local config exists → Electron uses hardcoded defaults.
|
||||
const script_template = get_launch_script_template(event_file_obj.extension);
|
||||
const launch_result = await native.launch_from_cache({
|
||||
cache_root,
|
||||
hash: event_file_obj.hash_sha256,
|
||||
temp_root,
|
||||
filename: event_file_obj.filename
|
||||
filename: event_file_obj.filename,
|
||||
script_template
|
||||
});
|
||||
|
||||
if (!launch_result.success) {
|
||||
|
||||
Reference in New Issue
Block a user