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 { ipcMain, shell } from 'electron';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
import * as os from 'os';
|
||||||
|
import { exec } from 'child_process';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { expandPath } from './file_utils';
|
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 source = get_organized_hashed_path(cache_root, hash, hash_prefix_length);
|
||||||
const expanded_temp = expandPath(temp_root);
|
const expanded_temp = expandPath(temp_root);
|
||||||
const target = path.join(expanded_temp, filename);
|
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 });
|
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);
|
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);
|
await shell.openPath(target);
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import * as path from 'path';
|
|||||||
|
|
||||||
export function expandPath(filePath: string): string {
|
export function expandPath(filePath: string): string {
|
||||||
if (!filePath) return filePath;
|
if (!filePath) return filePath;
|
||||||
if (filePath.startsWith('[home]')) {
|
// Resolve all instances of [home] and [tmp] using global regex
|
||||||
return path.join(os.homedir(), filePath.replace('[home]', ''));
|
return filePath
|
||||||
}
|
.replace(/\[home\]/g, os.homedir())
|
||||||
return filePath;
|
.replace(/\[tmp\]/g, os.tmpdir());
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getHashedPath(cacheRoot: string, hash: string): string {
|
export function getHashedPath(cacheRoot: string, hash: string): string {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ async function createWindow() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mainWindow = new BrowserWindow({
|
mainWindow = new BrowserWindow({
|
||||||
width: 1400,
|
width: 1600,
|
||||||
height: 900,
|
height: 900,
|
||||||
title: 'OSIT Aether Launcher (Native)',
|
title: 'OSIT Aether Launcher (Native)',
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
@@ -38,6 +38,7 @@ async function createWindow() {
|
|||||||
targetUrl = `http://${useHost}/events/${eventId}/launcher/${locationId}`;
|
targetUrl = `http://${useHost}/events/${eventId}/launcher/${locationId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mainWindow.webContents.openDevTools();
|
||||||
mainWindow.loadURL(targetUrl).catch(() => {
|
mainWindow.loadURL(targetUrl).catch(() => {
|
||||||
mainWindow?.loadURL('https://dev-demo.oneskyit.com/');
|
mainWindow?.loadURL('https://dev-demo.oneskyit.com/');
|
||||||
});
|
});
|
||||||
@@ -79,6 +80,8 @@ ipcMain.handle('get-device-info', async () => {
|
|||||||
cpus: os.cpus().length,
|
cpus: os.cpus().length,
|
||||||
total_mem: os.totalmem(),
|
total_mem: os.totalmem(),
|
||||||
free_mem: os.freemem(),
|
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 { exec, execSync } from 'child_process';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
|
import { expandPath } from './file_utils';
|
||||||
|
|
||||||
export function registerShellHandlers() {
|
export function registerShellHandlers() {
|
||||||
ipcMain.handle('native:open-folder', async (event, folderPath: string) => {
|
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 };
|
return { success: !error, error };
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle('native:run-cmd', async (event, { cmd, timeout = 30000 }) => {
|
ipcMain.handle('native:run-cmd', async (event, { cmd, timeout = 30000 }) => {
|
||||||
|
const cleanCmd = expandPath(cmd);
|
||||||
return new Promise((resolve) => {
|
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 });
|
resolve({ success: !error, stdout: stdout.trim(), stderr: stderr.trim(), error: error ? error.message : null });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle('native:run-cmd-sync', async (event, { cmd }) => {
|
ipcMain.handle('native:run-cmd-sync', async (event, { cmd }) => {
|
||||||
|
const cleanCmd = expandPath(cmd);
|
||||||
try {
|
try {
|
||||||
const stdout = execSync(cmd).toString();
|
const stdout = execSync(cleanCmd).toString();
|
||||||
return { success: true, stdout: stdout.trim() };
|
return { success: true, stdout: stdout.trim() };
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return { success: false, error: error.message, stderr: error.stderr?.toString() };
|
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) => {
|
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 };
|
return { success: !error, error };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,4 +16,5 @@ contextBridge.exposeInMainWorld('aetherNative', {
|
|||||||
check_cache: (args: any) => ipcRenderer.invoke('native:check-cache', args),
|
check_cache: (args: any) => ipcRenderer.invoke('native:check-cache', args),
|
||||||
download_to_cache: (args: any) => ipcRenderer.invoke('native:download-to-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_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