Files

895 lines
51 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Electron Desktop Target
**Phase ID:** PHASE-C
**Priority:** 🟡 Medium (Second Priority)
**Estimated Duration:** 3-4 weeks
**Status:** Planning
**Dependencies:** Phase A (BYOB Backend), Phase E (Target System Core)
**Last Updated:** 2025-12-28
## Executive Summary
Enable Noodl users to build native desktop applications for Windows, macOS, and Linux using Electron. Desktop apps unlock capabilities impossible in web browsers: file system access, system processes, native dialogs, and offline-first operation.
## Strategic Advantage
Noodl's editor is already built on Electron (`packages/noodl-platform-electron/`), providing deep institutional knowledge of Electron patterns, IPC communication, and native integration.
## Value Proposition
| Capability | Web | Electron Desktop |
|------------|-----|------------------|
| File System Access | ❌ Limited (File API) | ✅ Full read/write/watch |
| Run System Processes | ❌ No | ✅ Spawn any executable (FFmpeg, Ollama, Python) |
| Native Dialogs | ❌ Browser dialogs | ✅ OS-native file pickers, alerts |
| System Tray | ❌ No | ✅ Tray icon with menu |
| Desktop Notifications | ⚠️ Limited | ✅ Native OS notifications |
| Offline Operation | ⚠️ PWA only | ✅ Full offline support |
| No CORS Restrictions | ❌ Blocked | ✅ Direct API access |
| Auto-Updates | ❌ No | ✅ Built-in updater |
## Use Cases
1. **Local AI Applications** - Run Ollama, LM Studio, or other local LLMs
2. **File Processing Tools** - Batch rename, image conversion, video encoding
3. **Developer Tools** - Code generators, project scaffolders, CLI wrappers
4. **Data Analysis** - Process local CSV/Excel files, generate reports
5. **Automation Tools** - File watchers, backup utilities, sync tools
6. **Kiosk Applications** - Point of sale, digital signage, information displays
## Technical Architecture
### Electron Process Model
```
┌─────────────────────────────────────────────────────────────────────┐
│ ELECTRON APP │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ MAIN PROCESS │ │
│ │ (Node.js - full system access) │ │
│ │ │ │
│ │ • File system operations (fs) │ │
│ │ • Spawn child processes (child_process) │ │
│ │ • System dialogs (dialog) │ │
│ │ • System tray (Tray) │ │
│ │ • Auto-updates (autoUpdater) │ │
│ │ • Native menus (Menu) │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │ IPC │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ RENDERER PROCESS │ │
│ │ (Chromium - your Noodl app) │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
│ │ │ NOODL APP │ │ │
│ │ │ [File Read Node] ──▶ IPC ──▶ [Main Process] ──▶ fs.read │ │ │
│ │ │ [Run Process] ──▶ IPC ──▶ [Main Process] ──▶ spawn │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ Preload Script (Bridge between renderer and main) │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
```
### Security Model
Electron's security is critical. We use **context isolation** and **preload scripts**:
```typescript
// main.ts - Main process
const win = new BrowserWindow({
webPreferences: {
// Security settings
nodeIntegration: false, // Don't expose Node in renderer
contextIsolation: true, // Isolate preload from renderer
sandbox: true, // Sandbox renderer process
preload: path.join(__dirname, 'preload.js'),
}
});
// preload.ts - Secure bridge
const { contextBridge, ipcRenderer } = require('electron');
// Expose ONLY specific, validated APIs
contextBridge.exposeInMainWorld('electronAPI', {
// File operations (validated paths only)
readFile: (filePath: string) => {
// Validate path is within allowed directories
if (!isPathAllowed(filePath)) {
throw new Error('Access denied: path outside allowed directories');
}
return ipcRenderer.invoke('fs:readFile', filePath);
},
writeFile: (filePath: string, content: string) => {
if (!isPathAllowed(filePath)) {
throw new Error('Access denied');
}
return ipcRenderer.invoke('fs:writeFile', filePath, content);
},
// Dialog operations (safe - user-initiated)
showOpenDialog: (options) => ipcRenderer.invoke('dialog:open', options),
showSaveDialog: (options) => ipcRenderer.invoke('dialog:save', options),
// Process operations (controlled)
runProcess: (command: string, args: string[]) => {
// Only allow whitelisted commands
if (!isCommandAllowed(command)) {
throw new Error('Command not allowed');
}
return ipcRenderer.invoke('process:run', command, args);
},
});
```
## Electron-Specific Nodes
### File System Nodes
#### Read File
```
┌─────────────────────────────────────────────────────────────────────┐
│ 📄 Read File [🖥️] │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ INPUTS │
│ ─────────────────────────────────────────────────────────────────── │
│ │
│ Read ○──── (Signal) │
│ │
│ File Path: [ ] [📁] │
│ Or connect from File Picker output │
│ │
│ Encoding: [UTF-8 ▾] │
│ ├─ UTF-8 │
│ ├─ ASCII │
│ ├─ Base64 │
│ └─ Binary (Buffer) │
│ │
│ OUTPUTS │
│ ─────────────────────────────────────────────────────────────────── │
│ │
│ Content ────○ (String or Buffer) │
│ File Name ────○ (String) │
│ File Size ────○ (Number - bytes) │
│ Last Modified ────○ (Date) │
│ │
│ Success ────○ (Signal) │
│ Error ────○ (Signal) │
│ Error Message ────○ (String) │
│ │
└─────────────────────────────────────────────────────────────────────┘
```
#### Write File
```
┌─────────────────────────────────────────────────────────────────────┐
│ 💾 Write File [🖥️] │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ INPUTS │
│ ─────────────────────────────────────────────────────────────────── │
│ │
│ Write ○──── (Signal) │
│ │
│ File Path: [ ] [📁] │
│ Content: ○──── (String or Buffer) │
│ │
│ Encoding: [UTF-8 ▾] │
│ Create Directories: [✓] (Create parent dirs if missing) │
│ Overwrite: [✓] (Overwrite if exists) │
│ │
│ OUTPUTS │
│ ─────────────────────────────────────────────────────────────────── │
│ │
│ Written Path ────○ (String - full path) │
│ Success ────○ (Signal) │
│ Error ────○ (Signal) │
│ Error Message ────○ (String) │
│ │
└─────────────────────────────────────────────────────────────────────┘
```
#### Watch Directory
```
┌─────────────────────────────────────────────────────────────────────┐
│ 👁️ Watch Directory [🖥️] │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ INPUTS │
│ ─────────────────────────────────────────────────────────────────── │
│ │
│ Start Watching ○──── (Signal) │
│ Stop Watching ○──── (Signal) │
│ │
│ Directory Path: [ ] [📁] │
│ │
│ Watch Subdirectories: [✓] │
│ File Filter: [*.* ] (glob pattern) │
│ Debounce (ms): [100 ] │
│ │
│ OUTPUTS │
│ ─────────────────────────────────────────────────────────────────── │
│ │
│ File Path ────○ (String - changed file) │
│ Event Type ────○ (String - add/change/unlink) │
│ │
│ File Added ────○ (Signal) │
│ File Changed ────○ (Signal) │
│ File Removed ────○ (Signal) │
│ │
│ Is Watching ────○ (Boolean) │
│ Error ────○ (Signal) │
│ │
└─────────────────────────────────────────────────────────────────────┘
```
#### Native File Picker
```
┌─────────────────────────────────────────────────────────────────────┐
│ 📂 File Picker [🖥️] │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ INPUTS │
│ ─────────────────────────────────────────────────────────────────── │
│ │
│ Open Picker ○──── (Signal) │
│ │
│ Mode: [Open File ▾] │
│ ├─ Open File │
│ ├─ Open Multiple Files │
│ ├─ Open Directory │
│ └─ Save File │
│ │
│ Title: [Select a file ] │
│ │
│ File Types: [Images ▾] [+ Add] │
│ ┌─────────────────────────────────────┐ │
│ │ Name: Images │ │
│ │ Extensions: jpg, jpeg, png, gif │ │
│ └─────────────────────────────────────┘ │
│ │
│ Default Path: [/Users/richard/Documents ] [📁] │
│ │
│ OUTPUTS │
│ ─────────────────────────────────────────────────────────────────── │
│ │
│ Selected Path ────○ (String) │
│ Selected Paths ────○ (Array - for multi-select) │
│ File Name ────○ (String - just filename) │
│ │
│ Selected ────○ (Signal - user made selection) │
│ Cancelled ────○ (Signal - user cancelled) │
│ │
└─────────────────────────────────────────────────────────────────────┘
```
### Process Nodes
#### Run Process
```
┌─────────────────────────────────────────────────────────────────────┐
│ ⚙️ Run Process [🖥️] │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ INPUTS │
│ ─────────────────────────────────────────────────────────────────── │
│ │
│ Run ○──── (Signal) │
│ Kill ○──── (Signal - terminate process) │
│ │
│ Command: [ffmpeg ] │
│ │
│ Arguments: (Array of strings) │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ [-i, input.mp4, -c:v, libx264, output.mp4] │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ Or connect from Array node │
│ │
│ Working Directory: [/Users/richard/projects] [📁] │
│ │
│ Environment Variables: (Object) │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ { "PATH": "/usr/local/bin", "NODE_ENV": "production" } │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ Shell: [✓] (Run in shell - enables pipes) │
│ Timeout (ms): [0 ] (0 = no timeout) │
│ │
│ OUTPUTS │
│ ─────────────────────────────────────────────────────────────────── │
│ │
│ stdout ────○ (String - standard output) │
│ stderr ────○ (String - standard error) │
│ Exit Code ────○ (Number) │
│ │
│ On Output ────○ (Signal - fires on each line) │
│ Output Line ────○ (String - current output line) │
│ │
│ Started ────○ (Signal) │
│ Completed ────○ (Signal - exit code 0) │
│ Failed ────○ (Signal - non-zero exit) │
│ Is Running ────○ (Boolean) │
│ │
└─────────────────────────────────────────────────────────────────────┘
```
**Implementation:**
```typescript
// packages/noodl-runtime/src/nodes/electron/RunProcessNode.ts
import { spawn, ChildProcess } from 'child_process';
import { createNodeDefinition } from '../../nodeDefinition';
export const RunProcessNode = createNodeDefinition({
name: 'Run Process',
category: 'System',
color: 'red',
inputs: {
run: { type: 'signal', displayName: 'Run' },
kill: { type: 'signal', displayName: 'Kill' },
command: { type: 'string', displayName: 'Command' },
args: { type: 'array', displayName: 'Arguments', default: [] },
cwd: { type: 'string', displayName: 'Working Directory' },
env: { type: 'object', displayName: 'Environment Variables' },
shell: { type: 'boolean', displayName: 'Shell', default: false },
timeout: { type: 'number', displayName: 'Timeout (ms)', default: 0 },
},
outputs: {
stdout: { type: 'string', displayName: 'stdout' },
stderr: { type: 'string', displayName: 'stderr' },
exitCode: { type: 'number', displayName: 'Exit Code' },
onOutput: { type: 'signal', displayName: 'On Output' },
outputLine: { type: 'string', displayName: 'Output Line' },
started: { type: 'signal', displayName: 'Started' },
completed: { type: 'signal', displayName: 'Completed' },
failed: { type: 'signal', displayName: 'Failed' },
isRunning: { type: 'boolean', displayName: 'Is Running' },
},
targetCompatibility: ['electron'],
state: {
process: null as ChildProcess | null,
stdoutBuffer: '',
stderrBuffer: '',
},
signalHandlers: {
run: async function(inputs, outputs, state) {
// Validate command against whitelist (security)
if (!await this.validateCommand(inputs.command)) {
outputs.stderr = 'Command not allowed by security policy';
outputs.failed.trigger();
return;
}
const options = {
cwd: inputs.cwd || process.cwd(),
env: { ...process.env, ...inputs.env },
shell: inputs.shell,
timeout: inputs.timeout || undefined,
};
state.stdoutBuffer = '';
state.stderrBuffer = '';
outputs.isRunning = true;
state.process = spawn(inputs.command, inputs.args, options);
outputs.started.trigger();
state.process.stdout?.on('data', (data) => {
const text = data.toString();
state.stdoutBuffer += text;
outputs.stdout = state.stdoutBuffer;
// Emit line-by-line
const lines = text.split('\n');
for (const line of lines) {
if (line.trim()) {
outputs.outputLine = line;
outputs.onOutput.trigger();
}
}
});
state.process.stderr?.on('data', (data) => {
state.stderrBuffer += data.toString();
outputs.stderr = state.stderrBuffer;
});
state.process.on('close', (code) => {
outputs.exitCode = code ?? -1;
outputs.isRunning = false;
state.process = null;
if (code === 0) {
outputs.completed.trigger();
} else {
outputs.failed.trigger();
}
});
state.process.on('error', (err) => {
outputs.stderr = err.message;
outputs.isRunning = false;
outputs.failed.trigger();
});
},
kill: function(inputs, outputs, state) {
if (state.process) {
state.process.kill();
outputs.isRunning = false;
}
},
},
});
```
### Window Nodes
#### Window Control
```
┌─────────────────────────────────────────────────────────────────────┐
│ 🪟 Window Control [🖥️] │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ INPUTS │
│ ─────────────────────────────────────────────────────────────────── │
│ │
│ Minimize ○──── (Signal) │
│ Maximize ○──── (Signal) │
│ Restore ○──── (Signal) │
│ Close ○──── (Signal) │
│ Toggle Fullscreen ○──── (Signal) │
│ │
│ Set Size ○──── (Signal) │
│ Width: [800 ] │
│ Height: [600 ] │
│ │
│ Set Position ○──── (Signal) │
│ X: [100 ] │
│ Y: [100 ] │
│ │
│ Always On Top: [○] │
│ Resizable: [✓] │
│ │
│ OUTPUTS │
│ ─────────────────────────────────────────────────────────────────── │
│ │
│ Current Width ────○ (Number) │
│ Current Height ────○ (Number) │
│ Is Maximized ────○ (Boolean) │
│ Is Fullscreen ────○ (Boolean) │
│ Is Focused ────○ (Boolean) │
│ │
└─────────────────────────────────────────────────────────────────────┘
```
#### System Tray
```
┌─────────────────────────────────────────────────────────────────────┐
│ 🔔 System Tray [🖥️] │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ INPUTS │
│ ─────────────────────────────────────────────────────────────────── │
│ │
│ Show ○──── (Signal) │
│ Hide ○──── (Signal) │
│ │
│ Icon: [ ] [📁 Select Image] │
│ Tooltip: [My App ] │
│ │
│ Menu Items: (Array of menu items) │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ [ │ │
│ │ { "label": "Show Window", "action": "show" }, │ │
│ │ { "type": "separator" }, │ │
│ │ { "label": "Settings", "action": "settings" }, │ │
│ │ { "type": "separator" }, │ │
│ │ { "label": "Quit", "action": "quit" } │ │
│ │ ] │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ OUTPUTS │
│ ─────────────────────────────────────────────────────────────────── │
│ │
│ Menu Action ────○ (String - action from clicked item) │
│ Clicked ────○ (Signal - tray icon clicked) │
│ Double Clicked ────○ (Signal) │
│ │
└─────────────────────────────────────────────────────────────────────┘
```
### Additional Electron Nodes
| Node | Category | Description |
|------|----------|-------------|
| **Native Notification** | System | OS-native notifications with actions |
| **Clipboard** | Utility | Read/write clipboard (text, images) |
| **Screen Info** | System | Get display info, cursor position |
| **Power Monitor** | System | Battery status, suspend/resume events |
| **App Info** | System | Get app version, paths, locale |
| **Protocol Handler** | System | Register custom URL protocols |
| **Auto Launch** | System | Start app on system boot |
| **Global Shortcut** | Input | Register system-wide hotkeys |
## Preview Mode
### Electron Preview in Editor
When Electron target is selected, preview can run with Node.js integration enabled:
```
┌─────────────────────────────────────────────────────────────────────┐
│ Preview Mode: [Electron (Desktop) ▾] │
│ ├─ Web (Browser) │
│ └─ Electron (Desktop) ◀ │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ⚠️ ELECTRON PREVIEW MODE │
│ │
│ Preview is running with full desktop capabilities enabled. │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ ✓ File system access enabled │ │
│ │ ✓ Process execution enabled │ │
│ │ ✓ Native dialogs enabled │ │
│ │ ✓ System tray available │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ Security: Operations are sandboxed to your project directory │
│ │
│ [Switch to Web Preview] │
│ │
└─────────────────────────────────────────────────────────────────────┘
```
Since Noodl editor already runs in Electron, enabling desktop features in preview is straightforward:
```typescript
// In viewer.js when Electron preview mode is enabled
if (previewMode === 'electron') {
// Enable IPC bridge to main process
window.noodlElectronBridge = {
readFile: async (path) => ipcRenderer.invoke('fs:readFile', path),
writeFile: async (path, content) => ipcRenderer.invoke('fs:writeFile', path, content),
// ... other APIs
};
}
```
## Export Pipeline
### Export Dialog
```
┌─────────────────────────────────────────────────────────────────────┐
│ Export Desktop App [×] │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ PLATFORM │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ [Apple] │ │ [Windows] │ │ [Linux] │ │
│ │ macOS │ │ Windows │ │ Linux │ │
│ │ ✓ Selected │ │ ✓ Selected │ │ │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ─────────────────────────────────────────────────────────────────── │
│ │
│ APP CONFIGURATION │
│ │
│ App Name: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ My Desktop App │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ App ID: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ com.mycompany.mydesktopapp │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ Version: Build Number: │
│ ┌─────────────────────────┐ ┌─────────────────────────┐ │
│ │ 1.0.0 │ │ 1 │ │
│ └─────────────────────────┘ └─────────────────────────┘ │
│ │
│ ▶ macOS Settings │
│ Category: [Productivity ▾] │
│ Code Signing: [Developer ID ▾] │
│ Notarization: [✓] (Required for distribution) │
│ │
│ ▶ Windows Settings │
│ Code Signing: [None ▾] │
│ Installer Type: [NSIS ▾] │
│ │
│ ▶ App Icons │
│ ┌─────────────────┐ │
│ │ [App Icon] │ [📁 Select Icon] │
│ │ 512x512 PNG │ │
│ └─────────────────┘ │
│ ⓘ Will be converted to .icns (macOS) and .ico (Windows) │
│ │
│ ▶ Permissions │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ ☑ File System Access (Required by: Read File, ...) │ │
│ │ ☑ Process Execution (Required by: Run Process) │ │
│ │ ☐ Camera Access │ │
│ │ ☐ Microphone Access │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ OUTPUT │
│ ● Installable Package (.dmg, .exe, .deb) RECOMMENDED │
│ ○ Portable App (zip folder) │
│ ○ Development Build (unpackaged, for testing) │
│ │
│ [Cancel] [Build for macOS] │
│ [Build for Windows] │
│ [Build All] │
└─────────────────────────────────────────────────────────────────────┘
```
### Generated Project Structure
```
my-app-electron/
├── package.json # Electron app dependencies
├── electron-builder.yml # Build configuration
├── src/
│ ├── main/
│ │ ├── main.ts # Main process entry
│ │ ├── preload.ts # Preload script (IPC bridge)
│ │ ├── ipc/
│ │ │ ├── fs.ts # File system handlers
│ │ │ ├── dialog.ts # Dialog handlers
│ │ │ ├── process.ts # Process handlers
│ │ │ └── index.ts
│ │ └── menu.ts # Application menu
│ └── renderer/
│ ├── index.html # App entry point
│ ├── main.js # Noodl runtime bundle
│ ├── styles.css
│ └── assets/
├── resources/
│ ├── icon.icns # macOS icon
│ ├── icon.ico # Windows icon
│ └── icon.png # Linux icon
└── dist/ # Build output
├── mac/
│ └── My App.dmg
└── win/
└── My App Setup.exe
```
## Implementation Phases
### Phase C.1: Electron Runtime Package (1 week)
**Goal:** Create separate runtime package for Electron-specific functionality.
**Tasks:**
- [ ] Create `packages/noodl-runtime-electron/` package
- [ ] Implement secure IPC bridge (preload.ts)
- [ ] Implement main process handlers (fs, dialog, process)
- [ ] Create sandbox validation utilities
- [ ] Set up security policy configuration
**Files to Create:**
```
packages/noodl-runtime-electron/
├── package.json
├── tsconfig.json
├── src/
│ ├── index.ts
│ ├── main/
│ │ ├── main.ts # Main process entry
│ │ ├── preload.ts # Preload script
│ │ └── handlers/
│ │ ├── fs.ts
│ │ ├── dialog.ts
│ │ ├── process.ts
│ │ ├── window.ts
│ │ └── system.ts
│ ├── security/
│ │ ├── pathValidator.ts # Validate file paths
│ │ ├── commandWhitelist.ts # Allowed commands
│ │ └── permissions.ts
│ └── types.ts
└── test/
```
### Phase C.2: Electron-Specific Nodes (1 week)
**Goal:** Implement desktop capability nodes.
**Tasks:**
- [ ] File System nodes (Read, Write, Watch, Picker)
- [ ] Process nodes (Run, Kill)
- [ ] Window nodes (Control, Tray)
- [ ] System nodes (Notification, Clipboard, App Info)
- [ ] Register nodes in Electron target only
**Files to Create:**
```
packages/noodl-runtime/src/nodes/electron/
├── index.ts
├── fs/
│ ├── ReadFileNode.ts
│ ├── WriteFileNode.ts
│ ├── WatchDirectoryNode.ts
│ └── FilePickerNode.ts
├── process/
│ ├── RunProcessNode.ts
│ └── ProcessInfoNode.ts
├── window/
│ ├── WindowControlNode.ts
│ └── SystemTrayNode.ts
└── system/
├── NotificationNode.ts
├── ClipboardNode.ts
└── AppInfoNode.ts
```
### Phase C.3: Electron Preview Mode (3-4 days)
**Goal:** Enable desktop features in editor preview.
**Tasks:**
- [ ] Add Electron preview mode option
- [ ] Enable IPC bridge in preview window
- [ ] Create security sandbox for preview
- [ ] Add visual indicators for Electron mode
- [ ] Test all nodes in preview context
### Phase C.4: Electron Packaging (1 week)
**Goal:** Export production-ready desktop applications.
**Tasks:**
- [ ] Integrate electron-builder
- [ ] Generate main.ts from project configuration
- [ ] Generate preload.ts with used features
- [ ] Bundle Noodl app as renderer
- [ ] Configure code signing (macOS, Windows)
- [ ] Generate installer packages
- [ ] Create auto-update configuration
**Files to Create:**
```
packages/noodl-editor/src/editor/src/export/electron/
├── ElectronExporter.ts
├── mainGenerator.ts
├── preloadGenerator.ts
├── builderConfig.ts
├── iconConverter.ts
└── templates/
├── main.ts.template
├── preload.ts.template
└── electron-builder.yml.template
```
## Security Considerations
### Path Validation
All file system operations must validate paths:
```typescript
class PathValidator {
private allowedPaths: string[];
constructor(projectPath: string) {
this.allowedPaths = [
projectPath,
app.getPath('documents'),
app.getPath('downloads'),
app.getPath('temp'),
];
}
isPathAllowed(targetPath: string): boolean {
const resolved = path.resolve(targetPath);
// Check if path is within allowed directories
return this.allowedPaths.some(allowed =>
resolved.startsWith(path.resolve(allowed))
);
}
// Prevent path traversal attacks
sanitizePath(inputPath: string): string {
// Remove .. and normalize
return path.normalize(inputPath).replace(/\.\./g, '');
}
}
```
### Command Whitelist
Only allow specific commands to be executed:
```typescript
const ALLOWED_COMMANDS = [
// Media processing
'ffmpeg',
'ffprobe',
'imagemagick',
// AI/ML
'ollama',
'python',
'python3',
// Utilities
'git',
'npm',
'npx',
'node',
];
function isCommandAllowed(command: string): boolean {
const base = path.basename(command);
return ALLOWED_COMMANDS.includes(base);
}
```
### Permission System
```typescript
interface ElectronPermissions {
fileSystem: {
read: boolean;
write: boolean;
allowedPaths: string[];
};
process: {
execute: boolean;
allowedCommands: string[];
};
window: {
control: boolean;
tray: boolean;
};
system: {
notifications: boolean;
clipboard: boolean;
autoLaunch: boolean;
};
}
```
## Success Criteria
| Criteria | Target |
|----------|--------|
| Build time | < 2 minutes for production build |
| App size | < 150MB for minimal app |
| Startup time | < 3 seconds to first render |
| File operations | < 50ms overhead vs raw Node.js |
| All nodes tested | On macOS, Windows, Linux |
## Future Enhancements
1. **Native Node Modules** - Allow npm packages with native code
2. **Auto-Update System** - Built-in update mechanism
3. **Crash Reporting** - Integrate crash reporting service
4. **Hardware Access** - Serial ports, USB devices, Bluetooth
5. **Multiple Windows** - Open additional windows from nodes