Align launcher terminology docs
This commit is contained in:
29
README.md
29
README.md
@@ -10,6 +10,22 @@ This application serves as the "Native Mode" runtime for Aether podiums and devi
|
||||
- **Hardware Telemetry:** Direct access to CPU, RAM, and Network interface data.
|
||||
- **Remote Control:** Slide navigation and application control via WebSocket intents.
|
||||
|
||||
## Launcher Terminology
|
||||
|
||||
Use these terms consistently when working on the launcher bridge:
|
||||
|
||||
- **Launch Profiles**: the Svelte-side map keyed by file extension.
|
||||
- **Launch Profile**: one resolved config object selected from that map for a file.
|
||||
- **Native Template**: the single AppleScript or shell command string Electron executes after
|
||||
the file has been copied to temp. This is an implementation detail of the bridge.
|
||||
|
||||
In short, the profiles are the policy; the launch profile is the selected policy entry; the
|
||||
native template is the executable string produced by that policy.
|
||||
|
||||
Do not use `launch_scripts` as the public/config-facing term. If that wording appears in old
|
||||
comments or generated output, treat it as stale naming drift and update it to `launch_profiles`
|
||||
when referring to the Svelte-side map or `native_template` when referring to the resolved string.
|
||||
|
||||
## 🖥️ Onsite Deployment
|
||||
|
||||
**Current hardware:** MacBook Air 2018 — Intel x64. All current deployments use `aether_launcher-darwin-x64`.
|
||||
@@ -250,7 +266,7 @@ to change.
|
||||
| `check_cache({cache_root, hash, hash_prefix_length?, verify_hash?})` | Checks if a file exists in the hashed cache. `verify_hash: true` re-hashes the file to confirm integrity. |
|
||||
| `download_to_cache({url, cache_root, hash, api_key, account_id, hash_prefix_length?})` | Streams a file from the API into the hashed cache. Verifies SHA-256 integrity before finalizing. |
|
||||
| `copy_from_cache_to_temp({cache_root, hash, temp_root, filename, hash_prefix_length?})` | **Preferred primitive.** Copies cached file to temp dir with original filename. Returns `{ success, path }`. The Svelte caller decides what to do next. |
|
||||
| `launch_from_cache({cache_root, hash, temp_root, filename, hash_prefix_length?, script_template?})` | Combines copy + launch. If `script_template` is provided, runs it (AppleScript or `shell:` prefixed command) instead of hardcoded extension logic. Falls back to built-in defaults when `null`. |
|
||||
| `launch_from_cache({cache_root, hash, temp_root, filename, hash_prefix_length?, native_template?})` | Combines copy + launch. The Svelte side resolves the Launch Profile to a single `native_template` string (AppleScript or `shell:` prefixed command). If no template is supplied, it returns an error. |
|
||||
|
||||
### Shell & OS
|
||||
|
||||
@@ -313,18 +329,19 @@ await native.run_osascript(`
|
||||
await native.run_cmd({ cmd: `open "${copy.path}"` });
|
||||
|
||||
// Option C — use a template from device config (data-driven, no rebuild needed):
|
||||
const template = $ae_loc.native_device?.launch_scripts?.pptx;
|
||||
const template = $ae_loc.native_device?.launch_profiles?.pptx;
|
||||
if (template) {
|
||||
const script = template.replace(/\{\{path\}\}/g, copy.path);
|
||||
await native.run_osascript(script);
|
||||
}
|
||||
```
|
||||
|
||||
### Configurable Launch Scripts (no rebuild needed)
|
||||
### Configurable Launch Profiles (no rebuild needed)
|
||||
|
||||
`launch_from_cache` and `launcher_file_cont.svelte` support per-extension script templates
|
||||
stored in `event_device.data_json.launch_scripts`. Keys are lowercase extensions (`pptx`, `key`,
|
||||
`pdf`, etc.); `default` is a catch-all. Templates use `{{path}}` as the file path placeholder.
|
||||
`launch_profiles` is the Svelte-side map stored in `event_device.data_json.launch_profiles`.
|
||||
`launch_from_cache` receives the resolved `native_template` string, not the profile map.
|
||||
Keys are lowercase extensions (`pptx`, `key`, `pdf`, etc.); `default` is a catch-all.
|
||||
Profiles use `{{path}}` as the file path placeholder.
|
||||
AppleScript strings run via `run_osascript`; prefix with `shell:` for shell commands.
|
||||
|
||||
See `documentation/PROJECT__AE_Events_Launcher_Native_integration.md` Section 8 for full details.
|
||||
|
||||
146
dist/main/file_handlers.js
vendored
146
dist/main/file_handlers.js
vendored
@@ -130,7 +130,7 @@ function registerFileHandlers() {
|
||||
endpoints_in_progress = endpoints_in_progress.filter(e => e !== url);
|
||||
}
|
||||
});
|
||||
electron_1.ipcMain.handle('native:launch-from-cache', async (event, { cache_root, hash, temp_root, filename, hash_prefix_length = 2, script_template = null }) => {
|
||||
electron_1.ipcMain.handle('native:launch-from-cache', async (event, { cache_root, hash, temp_root, filename, hash_prefix_length = 2, launch_profiles = null }) => {
|
||||
try {
|
||||
const source = get_organized_hashed_path(cache_root, hash, hash_prefix_length);
|
||||
const expanded_temp = (0, file_utils_1.expandPath)(temp_root);
|
||||
@@ -143,118 +143,46 @@ function registerFileHandlers() {
|
||||
fs.mkdirSync(expanded_temp, { recursive: true });
|
||||
// 1. Copy the file to temp folder with original name
|
||||
fs.copyFileSync(source, target);
|
||||
// 2a. Data-driven script override (no rebuild needed for script changes).
|
||||
// Set via event_device.data_json.launch_scripts or $events_loc.launcher.launch_scripts.
|
||||
// 2a. Data-driven launcher profile (no rebuild needed for config changes).
|
||||
// Set via event_device.data_json.launch_profiles or $events_loc.launcher.launch_profiles.
|
||||
// Format: AppleScript string with {{path}} placeholder, OR "shell:<cmd> {{path}}"
|
||||
if (script_template) {
|
||||
const resolved = script_template.replace(/\{\{path\}\}/g, target);
|
||||
if (resolved.startsWith('shell:')) {
|
||||
const cmd = resolved.slice(6).trim();
|
||||
console.log(`Native: Running custom shell script for ${filename}`);
|
||||
return new Promise((resolve_fn) => {
|
||||
(0, child_process_1.exec)(cmd, (err, stdout, stderr) => {
|
||||
if (err)
|
||||
resolve_fn({ success: false, error: err.message, stderr: stderr.trim() });
|
||||
else
|
||||
resolve_fn({ success: true, stdout: stdout.trim() });
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
// Treat as AppleScript — write to temp .scpt file (same hardened approach used below)
|
||||
console.log(`Native: Running custom AppleScript for ${filename}`);
|
||||
const tmp_script_path = path.join(os.tmpdir(), `ae_launch_${Date.now()}.scpt`);
|
||||
return new Promise((resolve_fn) => {
|
||||
try {
|
||||
fs.writeFileSync(tmp_script_path, resolved.trim());
|
||||
}
|
||||
catch (e) {
|
||||
resolve_fn({ success: false, error: `Failed to write AppleScript temp file: ${e.message}` });
|
||||
return;
|
||||
}
|
||||
(0, child_process_1.exec)(`osascript "${tmp_script_path}"`, (err) => {
|
||||
try {
|
||||
fs.unlinkSync(tmp_script_path);
|
||||
}
|
||||
catch { }
|
||||
if (err)
|
||||
resolve_fn({ success: false, error: err.message });
|
||||
else
|
||||
resolve_fn({ success: true });
|
||||
});
|
||||
});
|
||||
}
|
||||
if (!launch_profiles) {
|
||||
return { success: false, error: 'No launch profile configured for this file' };
|
||||
}
|
||||
// 2b. Determine file type (legacy hardcoded launch logic — used when no script_template provided)
|
||||
const ext = path.extname(filename).toLowerCase().replace('.', '');
|
||||
const is_pres = ['pptx', 'ppt', 'key', 'pdf', 'odp'].includes(ext);
|
||||
// 3. Hardcoded launch (legacy — still the default when no script_template is configured)
|
||||
if (is_pres) {
|
||||
if (os.platform() === 'linux') {
|
||||
console.log(`Native: Launching LibreOffice (--impress) for ${target}`);
|
||||
return new Promise((resolve) => {
|
||||
(0, child_process_1.exec)(`libreoffice --impress "${target}"`, (err) => {
|
||||
if (err)
|
||||
resolve({ success: false, error: err.message });
|
||||
else
|
||||
resolve({ success: true });
|
||||
});
|
||||
const resolved = launch_profiles.replace(/\{\{path\}\}/g, target);
|
||||
if (resolved.startsWith('shell:')) {
|
||||
const cmd = resolved.slice(6).trim();
|
||||
console.log(`Native: Running custom shell profile for ${filename}`);
|
||||
return new Promise((resolve_fn) => {
|
||||
(0, child_process_1.exec)(cmd, (err, stdout, stderr) => {
|
||||
if (err)
|
||||
resolve_fn({ success: false, error: err.message, stderr: stderr.trim() });
|
||||
else
|
||||
resolve_fn({ success: true, stdout: stdout.trim() });
|
||||
});
|
||||
}
|
||||
if (os.platform() === 'darwin') {
|
||||
let script = '';
|
||||
if (ext === 'key') {
|
||||
script = `
|
||||
tell application "Keynote"
|
||||
activate
|
||||
open (POSIX file "${target}")
|
||||
delay 1
|
||||
start (front document)
|
||||
end tell
|
||||
`;
|
||||
}
|
||||
else if (ext === 'pptx' || ext === 'ppt') {
|
||||
script = `
|
||||
tell application "Microsoft PowerPoint"
|
||||
activate
|
||||
open (POSIX file "${target}")
|
||||
delay 3
|
||||
end tell
|
||||
tell application "System Events"
|
||||
keystroke return using command down
|
||||
end tell
|
||||
`;
|
||||
}
|
||||
if (script) {
|
||||
console.log(`Native: Launching ${ext} via AppleScript for ${target}`);
|
||||
// Write to a temp .scpt file instead of passing via -e flag.
|
||||
// The -e approach breaks on multi-line scripts and paths with spaces or quotes.
|
||||
const tmp_script_path = path.join(os.tmpdir(), `ae_launch_${Date.now()}.scpt`);
|
||||
return new Promise((resolve) => {
|
||||
try {
|
||||
fs.writeFileSync(tmp_script_path, script.trim());
|
||||
}
|
||||
catch (e) {
|
||||
resolve({ success: false, error: `Failed to write AppleScript temp file: ${e.message}` });
|
||||
return;
|
||||
}
|
||||
(0, child_process_1.exec)(`osascript "${tmp_script_path}"`, (err) => {
|
||||
try {
|
||||
fs.unlinkSync(tmp_script_path);
|
||||
}
|
||||
catch { }
|
||||
if (err)
|
||||
resolve({ success: false, error: err.message });
|
||||
else
|
||||
resolve({ success: true });
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// 4. Default Fallback
|
||||
await electron_1.shell.openPath(target);
|
||||
return { success: true };
|
||||
console.log(`Native: Running custom AppleScript profile for ${filename}`);
|
||||
const tmp_script_path = path.join(os.tmpdir(), `ae_launch_${Date.now()}.scpt`);
|
||||
return new Promise((resolve_fn) => {
|
||||
try {
|
||||
fs.writeFileSync(tmp_script_path, resolved.trim());
|
||||
}
|
||||
catch (e) {
|
||||
resolve_fn({ success: false, error: `Failed to write AppleScript temp file: ${e.message}` });
|
||||
return;
|
||||
}
|
||||
(0, child_process_1.exec)(`osascript "${tmp_script_path}"`, (err) => {
|
||||
try {
|
||||
fs.unlinkSync(tmp_script_path);
|
||||
}
|
||||
catch { }
|
||||
if (err)
|
||||
resolve_fn({ success: false, error: err.message });
|
||||
else
|
||||
resolve_fn({ success: true });
|
||||
});
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
return { success: false, error: error.message };
|
||||
|
||||
2
dist/main/file_handlers.js.map
vendored
2
dist/main/file_handlers.js.map
vendored
File diff suppressed because one or more lines are too long
@@ -7,6 +7,20 @@
|
||||
- We now know the API side was not the root cause. The bootstrap request shape in `src/main/api_client.ts` was wrong and has been corrected.
|
||||
- The packaging blocker has been diagnosed and fixed (see below).
|
||||
|
||||
## Launcher Terminology Cleanup
|
||||
- Align the native docs/comments with the Svelte-side terminology: **Launch Profile** = the
|
||||
Svelte config object keyed by extension; **Native Template** = the AppleScript or shell
|
||||
string Electron actually executes after the file lands in temp.
|
||||
- Update `src/main/file_handlers.ts` comments and any bridge-facing wording so they describe the
|
||||
resolved template string accurately. The current IPC arg name in source is `launch_profiles`,
|
||||
but it carries a single native template string. Do not reintroduce `launch_scripts` as the
|
||||
public term.
|
||||
- Keep the source of truth in Svelte. Electron should remain a thin executor/copy layer.
|
||||
- After source/docs updates, rebuild/regenerate `dist/main/file_handlers.js` from source; do not
|
||||
hand-edit the generated file.
|
||||
- If any parameter names remain awkward in source, preserve runtime behavior first and rename
|
||||
only when the signature ripple is understood.
|
||||
|
||||
## What Was Fixed
|
||||
- Updated the native bootstrap flow to use the direct `site_domain/search` request body expected by API V3.
|
||||
- Standardized the account-bypass header to `x-no-account-id: bypass` where that narrow bypass is intended.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ipcMain, shell } from 'electron';
|
||||
import { ipcMain } from 'electron';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
@@ -100,7 +100,7 @@ export function registerFileHandlers() {
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('native:launch-from-cache', async (event, { cache_root, hash, temp_root, filename, hash_prefix_length = 2, script_template = null }) => {
|
||||
ipcMain.handle('native:launch-from-cache', async (event, { cache_root, hash, temp_root, filename, hash_prefix_length = 2, launch_profiles = null }) => {
|
||||
try {
|
||||
const source = get_organized_hashed_path(cache_root, hash, hash_prefix_length);
|
||||
const expanded_temp = expandPath(temp_root);
|
||||
@@ -117,105 +117,41 @@ export function registerFileHandlers() {
|
||||
// 1. Copy the file to temp folder with original name
|
||||
fs.copyFileSync(source, target);
|
||||
|
||||
// 2a. Data-driven script override (no rebuild needed for script changes).
|
||||
// Set via event_device.data_json.launch_scripts or $events_loc.launcher.launch_scripts.
|
||||
// 2a. Data-driven launcher profile (no rebuild needed for config changes).
|
||||
// Set via event_device.data_json.launch_profiles or $events_loc.launcher.launch_profiles.
|
||||
// Format: AppleScript string with {{path}} placeholder, OR "shell:<cmd> {{path}}"
|
||||
if (script_template) {
|
||||
const resolved = (script_template as string).replace(/\{\{path\}\}/g, target);
|
||||
if (resolved.startsWith('shell:')) {
|
||||
const cmd = resolved.slice(6).trim();
|
||||
console.log(`Native: Running custom shell script for ${filename}`);
|
||||
return new Promise((resolve_fn) => {
|
||||
exec(cmd, (err, stdout, stderr) => {
|
||||
if (err) resolve_fn({ success: false, error: err.message, stderr: stderr.trim() });
|
||||
else resolve_fn({ success: true, stdout: stdout.trim() });
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Treat as AppleScript — write to temp .scpt file (same hardened approach used below)
|
||||
console.log(`Native: Running custom AppleScript for ${filename}`);
|
||||
const tmp_script_path = path.join(os.tmpdir(), `ae_launch_${Date.now()}.scpt`);
|
||||
return new Promise((resolve_fn) => {
|
||||
try {
|
||||
fs.writeFileSync(tmp_script_path, resolved.trim());
|
||||
} catch (e: any) {
|
||||
resolve_fn({ success: false, error: `Failed to write AppleScript temp file: ${e.message}` });
|
||||
return;
|
||||
}
|
||||
exec(`osascript "${tmp_script_path}"`, (err) => {
|
||||
try { fs.unlinkSync(tmp_script_path); } catch {}
|
||||
if (err) resolve_fn({ success: false, error: err.message });
|
||||
else resolve_fn({ success: true });
|
||||
});
|
||||
});
|
||||
}
|
||||
if (!launch_profiles) {
|
||||
return { success: false, error: 'No launch profile configured for this file' };
|
||||
}
|
||||
|
||||
// 2b. Determine file type (legacy hardcoded launch logic — used when no script_template provided)
|
||||
const ext = path.extname(filename).toLowerCase().replace('.', '');
|
||||
const is_pres = ['pptx', 'ppt', 'key', 'pdf', 'odp'].includes(ext);
|
||||
|
||||
// 3. Hardcoded launch (legacy — still the default when no script_template is configured)
|
||||
if (is_pres) {
|
||||
if (os.platform() === 'linux') {
|
||||
console.log(`Native: Launching LibreOffice (--impress) for ${target}`);
|
||||
return new Promise((resolve) => {
|
||||
exec(`libreoffice --impress "${target}"`, (err) => {
|
||||
if (err) resolve({ success: false, error: err.message });
|
||||
else resolve({ success: true });
|
||||
});
|
||||
const resolved = (launch_profiles as string).replace(/\{\{path\}\}/g, target);
|
||||
if (resolved.startsWith('shell:')) {
|
||||
const cmd = resolved.slice(6).trim();
|
||||
console.log(`Native: Running custom shell profile for ${filename}`);
|
||||
return new Promise((resolve_fn) => {
|
||||
exec(cmd, (err, stdout, stderr) => {
|
||||
if (err) resolve_fn({ success: false, error: err.message, stderr: stderr.trim() });
|
||||
else resolve_fn({ success: true, stdout: stdout.trim() });
|
||||
});
|
||||
}
|
||||
|
||||
if (os.platform() === 'darwin') {
|
||||
let script = '';
|
||||
if (ext === 'key') {
|
||||
script = `
|
||||
tell application "Keynote"
|
||||
activate
|
||||
open (POSIX file "${target}")
|
||||
delay 1
|
||||
start (front document)
|
||||
end tell
|
||||
`;
|
||||
} else if (ext === 'pptx' || ext === 'ppt') {
|
||||
script = `
|
||||
tell application "Microsoft PowerPoint"
|
||||
activate
|
||||
open (POSIX file "${target}")
|
||||
delay 3
|
||||
end tell
|
||||
tell application "System Events"
|
||||
keystroke return using command down
|
||||
end tell
|
||||
`;
|
||||
}
|
||||
|
||||
if (script) {
|
||||
console.log(`Native: Launching ${ext} via AppleScript for ${target}`);
|
||||
// Write to a temp .scpt file instead of passing via -e flag.
|
||||
// The -e approach breaks on multi-line scripts and paths with spaces or quotes.
|
||||
const tmp_script_path = path.join(os.tmpdir(), `ae_launch_${Date.now()}.scpt`);
|
||||
return new Promise((resolve) => {
|
||||
try {
|
||||
fs.writeFileSync(tmp_script_path, script.trim());
|
||||
} catch (e: any) {
|
||||
resolve({ success: false, error: `Failed to write AppleScript temp file: ${e.message}` });
|
||||
return;
|
||||
}
|
||||
exec(`osascript "${tmp_script_path}"`, (err) => {
|
||||
try { fs.unlinkSync(tmp_script_path); } catch {}
|
||||
if (err) resolve({ success: false, error: err.message });
|
||||
else resolve({ success: true });
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 4. Default Fallback
|
||||
await shell.openPath(target);
|
||||
return { success: true };
|
||||
console.log(`Native: Running custom AppleScript profile for ${filename}`);
|
||||
const tmp_script_path = path.join(os.tmpdir(), `ae_launch_${Date.now()}.scpt`);
|
||||
return new Promise((resolve_fn) => {
|
||||
try {
|
||||
fs.writeFileSync(tmp_script_path, resolved.trim());
|
||||
} catch (e: any) {
|
||||
resolve_fn({ success: false, error: `Failed to write AppleScript temp file: ${e.message}` });
|
||||
return;
|
||||
}
|
||||
exec(`osascript "${tmp_script_path}"`, (err) => {
|
||||
try { fs.unlinkSync(tmp_script_path); } catch {}
|
||||
if (err) resolve_fn({ success: false, error: err.message });
|
||||
else resolve_fn({ success: true });
|
||||
});
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export interface AetherNativeBridge {
|
||||
get_device_config: () => Promise<any>;
|
||||
get_jwt: () => Promise<string | null>;
|
||||
get_device_info: () => Promise<any>;
|
||||
|
||||
|
||||
// Shell Handlers
|
||||
open_folder: (path: string) => Promise<{success: boolean, error?: string}>;
|
||||
run_cmd: (args: {cmd: string, timeout?: number}) => Promise<{success: boolean, stdout: string, stderr: string, error?: string}>;
|
||||
@@ -24,7 +24,7 @@ export interface AetherNativeBridge {
|
||||
check_cache: (args: {cache_root: string, hash: string, hash_prefix_length?: number}) => Promise<boolean>;
|
||||
download_to_cache: (args: {url: string, cache_root: string, hash: string, api_key: string, account_id?: string, hash_prefix_length?: number}) => Promise<{success: boolean, error?: string}>;
|
||||
copy_from_cache_to_temp: (args: {cache_root: string, hash: string, temp_root: string, filename: string, hash_prefix_length?: number}) => Promise<{success: boolean, path?: string, error?: string}>;
|
||||
launch_from_cache: (args: {cache_root: string, hash: string, temp_root: string, filename: string, hash_prefix_length?: number, script_template?: string}) => Promise<{success: boolean, error?: string}>;
|
||||
launch_from_cache: (args: {cache_root: string, hash: string, temp_root: string, filename: string, hash_prefix_length?: number, launch_profiles?: string}) => Promise<{success: boolean, error?: string}>;
|
||||
|
||||
// Specialized Presentation Handlers (Phase 5)
|
||||
launch_presentation: (args: {path: string, app?: string}) => Promise<{success: boolean, error?: string, stdout?: string, stderr?: string}>;
|
||||
|
||||
Reference in New Issue
Block a user