feat(bridge): implement presentation-aware handover and robust placeholder resolution
- Upgraded launch_from_cache to automatically trigger LibreOffice/AppleScript launchers after file copy. - Hardened expandPath to resolve [home] and [tmp] placeholders anywhere in strings via global regex. - Enhanced get-device-info telemetry to provide absolute home and tmp paths to the UI. - Exposed native:launch-presentation in preload and implemented explicit LibreOffice (--impress) support on Linux.
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import { ipcMain, shell } from 'electron';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import { exec } from 'child_process';
|
||||
import axios from 'axios';
|
||||
import { expandPath } from './file_utils';
|
||||
|
||||
@@ -64,8 +66,43 @@ export function registerFileHandlers() {
|
||||
const source = get_organized_hashed_path(cache_root, hash, hash_prefix_length);
|
||||
const expanded_temp = expandPath(temp_root);
|
||||
const target = path.join(expanded_temp, filename);
|
||||
|
||||
console.log(`Native: Launching from Cache -> ${filename}`);
|
||||
|
||||
if (!fs.existsSync(expanded_temp)) fs.mkdirSync(expanded_temp, { recursive: true });
|
||||
|
||||
// 1. Copy the file to temp folder with original name
|
||||
fs.copyFileSync(source, target);
|
||||
|
||||
// 2. Determine file type
|
||||
const ext = path.extname(filename).toLowerCase().replace('.', '');
|
||||
const is_pres = ['pptx', 'ppt', 'key', 'pdf', 'odp'].includes(ext);
|
||||
|
||||
// 3. Optimized Launch (LibreOffice / AppleScript)
|
||||
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 });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (os.platform() === 'darwin' && ext === 'key') {
|
||||
console.log(`Native: Launching Keynote via AppleScript for ${target}`);
|
||||
const script = `tell application "Keynote" to open POSIX file "${target}"`;
|
||||
return new Promise((resolve) => {
|
||||
exec(`osascript -e "${script.replace(/"/g, '\"')}"`, (err) => {
|
||||
if (err) resolve({ success: false, error: err.message });
|
||||
else resolve({ success: true });
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Default Fallback
|
||||
await shell.openPath(target);
|
||||
return { success: true };
|
||||
} catch (error: any) {
|
||||
|
||||
@@ -3,10 +3,10 @@ import * as path from 'path';
|
||||
|
||||
export function expandPath(filePath: string): string {
|
||||
if (!filePath) return filePath;
|
||||
if (filePath.startsWith('[home]')) {
|
||||
return path.join(os.homedir(), filePath.replace('[home]', ''));
|
||||
}
|
||||
return filePath;
|
||||
// Resolve all instances of [home] and [tmp] using global regex
|
||||
return filePath
|
||||
.replace(/\[home\]/g, os.homedir())
|
||||
.replace(/\[tmp\]/g, os.tmpdir());
|
||||
}
|
||||
|
||||
export function getHashedPath(cacheRoot: string, hash: string): string {
|
||||
|
||||
@@ -18,7 +18,7 @@ async function createWindow() {
|
||||
}
|
||||
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1400,
|
||||
width: 1600,
|
||||
height: 900,
|
||||
title: 'OSIT Aether Launcher (Native)',
|
||||
webPreferences: {
|
||||
@@ -38,6 +38,7 @@ async function createWindow() {
|
||||
targetUrl = `http://${useHost}/events/${eventId}/launcher/${locationId}`;
|
||||
}
|
||||
|
||||
mainWindow.webContents.openDevTools();
|
||||
mainWindow.loadURL(targetUrl).catch(() => {
|
||||
mainWindow?.loadURL('https://dev-demo.oneskyit.com/');
|
||||
});
|
||||
@@ -79,6 +80,8 @@ ipcMain.handle('get-device-info', async () => {
|
||||
cpus: os.cpus().length,
|
||||
total_mem: os.totalmem(),
|
||||
free_mem: os.freemem(),
|
||||
ip_addresses: addresses
|
||||
ip_addresses: addresses,
|
||||
home_directory: os.homedir(),
|
||||
tmp_directory: os.tmpdir()
|
||||
};
|
||||
});
|
||||
|
||||
@@ -2,24 +2,28 @@ 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 error = await shell.openPath(folderPath);
|
||||
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(cmd, { timeout }, (error, stdout, stderr) => {
|
||||
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(cmd).toString();
|
||||
const stdout = execSync(cleanCmd).toString();
|
||||
return { success: true, stdout: stdout.trim() };
|
||||
} catch (error: any) {
|
||||
return { success: false, error: error.message, stderr: error.stderr?.toString() };
|
||||
@@ -55,7 +59,39 @@ export function registerShellHandlers() {
|
||||
});
|
||||
|
||||
ipcMain.handle('native:open-local-file-v2', async (event, filePath: string) => {
|
||||
const error = await shell.openPath(filePath);
|
||||
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') {
|
||||
if (appType === 'keynote') {
|
||||
const script = `tell application "Keynote" to open POSIX file "${cleanedPath}"`;
|
||||
const escapedScript = script.replace(/"/g, '\\"');
|
||||
return new Promise((resolve) => {
|
||||
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 };
|
||||
});
|
||||
}
|
||||
|
||||
@@ -16,4 +16,5 @@ contextBridge.exposeInMainWorld('aetherNative', {
|
||||
check_cache: (args: any) => ipcRenderer.invoke('native:check-cache', args),
|
||||
download_to_cache: (args: any) => ipcRenderer.invoke('native:download-to-cache', args),
|
||||
launch_from_cache: (args: any) => ipcRenderer.invoke('native:launch-from-cache', args),
|
||||
launch_presentation: (args: any) => ipcRenderer.invoke('native:launch-presentation', args),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user