aegis-framework 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/aegis/cli/cli.py
CHANGED
|
@@ -434,48 +434,266 @@ console.log('✅ Preload configured');
|
|
|
434
434
|
# README
|
|
435
435
|
readme_content = f'''# {project_name}
|
|
436
436
|
|
|
437
|
-
An application built with [Aegis Framework](https://github.com/
|
|
437
|
+
An application built with [Aegis Framework](https://github.com/Diegopam/aegis-framework) - the lightweight alternative to Electron!
|
|
438
438
|
|
|
439
|
-
##
|
|
439
|
+
## 🚀 Quick Start
|
|
440
440
|
|
|
441
441
|
```bash
|
|
442
|
-
# Development mode
|
|
442
|
+
# Development mode (with hot-reload)
|
|
443
443
|
aegis dev
|
|
444
444
|
|
|
445
|
-
#
|
|
445
|
+
# Run in production mode
|
|
446
|
+
aegis run
|
|
447
|
+
|
|
448
|
+
# Build AppImage (~200KB!)
|
|
446
449
|
aegis build
|
|
447
450
|
```
|
|
448
451
|
|
|
449
|
-
## Project Structure
|
|
452
|
+
## 📁 Project Structure
|
|
450
453
|
|
|
451
454
|
```
|
|
452
455
|
{project_name}/
|
|
453
|
-
├── aegis.config.json #
|
|
456
|
+
├── aegis.config.json # App configuration (size, title, etc.)
|
|
454
457
|
├── src/
|
|
455
|
-
│ ├── index.html # Main HTML
|
|
456
|
-
│ ├── styles.css #
|
|
457
|
-
│ ├── app.js # JavaScript
|
|
458
|
-
│ └── preload.js # Security
|
|
458
|
+
│ ├── index.html # Main HTML entry point
|
|
459
|
+
│ ├── styles.css # Your styles
|
|
460
|
+
│ ├── app.js # Your JavaScript code
|
|
461
|
+
│ └── preload.js # Security: control which APIs are exposed
|
|
459
462
|
└── assets/
|
|
460
|
-
└── icon.png # App icon
|
|
463
|
+
└── icon.png # App icon (256x256 recommended)
|
|
461
464
|
```
|
|
462
465
|
|
|
463
|
-
## Aegis API
|
|
466
|
+
## 🔌 Aegis API Reference
|
|
467
|
+
|
|
468
|
+
### File Operations
|
|
464
469
|
|
|
465
470
|
```javascript
|
|
466
|
-
// Read
|
|
467
|
-
const
|
|
471
|
+
// Read directory contents
|
|
472
|
+
const dir = await Aegis.read({{ path: '/home/user' }});
|
|
473
|
+
console.log(dir.entries); // [{{ name: 'file.txt', isFile: true, size: 1234 }}, ...]
|
|
474
|
+
|
|
475
|
+
// Read file content
|
|
476
|
+
const file = await Aegis.read({{ path: '/home/user', file: 'data.txt' }});
|
|
477
|
+
console.log(file.content);
|
|
478
|
+
|
|
479
|
+
// Write file
|
|
480
|
+
await Aegis.write({{
|
|
481
|
+
path: '/home/user',
|
|
482
|
+
file: 'output.txt',
|
|
483
|
+
content: 'Hello, Aegis!'
|
|
484
|
+
}});
|
|
485
|
+
|
|
486
|
+
// Check if file/directory exists
|
|
487
|
+
const info = await Aegis.exists({{ path: '/home/user/file.txt' }});
|
|
488
|
+
if (info.exists && info.isFile) {{
|
|
489
|
+
console.log('File exists!');
|
|
490
|
+
}}
|
|
491
|
+
|
|
492
|
+
// Create directory
|
|
493
|
+
await Aegis.mkdir({{ path: '/home/user/new-folder' }});
|
|
494
|
+
|
|
495
|
+
// Delete file or directory
|
|
496
|
+
await Aegis.remove({{ path: '/home/user/old-file.txt' }});
|
|
497
|
+
await Aegis.remove({{ path: '/home/user/old-folder', recursive: true }});
|
|
498
|
+
|
|
499
|
+
// Copy file or directory
|
|
500
|
+
await Aegis.copy({{
|
|
501
|
+
src: '/home/user/file.txt',
|
|
502
|
+
dest: '/home/user/backup/file.txt'
|
|
503
|
+
}});
|
|
504
|
+
|
|
505
|
+
// Move/rename file or directory
|
|
506
|
+
await Aegis.move({{
|
|
507
|
+
src: '/home/user/old-name.txt',
|
|
508
|
+
dest: '/home/user/new-name.txt'
|
|
509
|
+
}});
|
|
510
|
+
```
|
|
468
511
|
|
|
469
|
-
|
|
470
|
-
await Aegis.write({{ path: '.', file: 'output.txt', content: 'Hello!' }});
|
|
512
|
+
### Execute Commands
|
|
471
513
|
|
|
472
|
-
|
|
514
|
+
```javascript
|
|
515
|
+
// Run shell command
|
|
473
516
|
const result = await Aegis.run({{ sh: 'ls -la' }});
|
|
517
|
+
console.log(result.output);
|
|
518
|
+
console.log(result.exitCode);
|
|
519
|
+
|
|
520
|
+
// Run Python code
|
|
521
|
+
const pyResult = await Aegis.run({{ py: 'print(2 + 2)' }});
|
|
522
|
+
console.log(pyResult.output); // "4"
|
|
523
|
+
|
|
524
|
+
// Run async command with streaming output (no UI freeze!)
|
|
525
|
+
await Aegis.runAsync(
|
|
526
|
+
{{ sh: 'apt update' }},
|
|
527
|
+
(progress) => {{
|
|
528
|
+
console.log(progress.line); // Each line as it comes
|
|
529
|
+
}}
|
|
530
|
+
);
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
### Dialogs
|
|
474
534
|
|
|
475
|
-
|
|
476
|
-
|
|
535
|
+
```javascript
|
|
536
|
+
// Info dialog
|
|
537
|
+
await Aegis.dialog.message({{
|
|
538
|
+
type: 'info',
|
|
539
|
+
title: 'Success',
|
|
540
|
+
message: 'Operation completed!'
|
|
541
|
+
}});
|
|
542
|
+
|
|
543
|
+
// Confirmation dialog
|
|
544
|
+
const confirm = await Aegis.dialog.message({{
|
|
545
|
+
type: 'question',
|
|
546
|
+
title: 'Confirm',
|
|
547
|
+
message: 'Are you sure?',
|
|
548
|
+
buttons: 'yesno'
|
|
549
|
+
}});
|
|
550
|
+
if (confirm.response) {{
|
|
551
|
+
// User clicked Yes
|
|
552
|
+
}}
|
|
553
|
+
|
|
554
|
+
// Open file dialog
|
|
555
|
+
const file = await Aegis.dialog.open({{
|
|
556
|
+
title: 'Select a file',
|
|
557
|
+
filters: [{{ name: 'Images', extensions: ['png', 'jpg', 'gif'] }}]
|
|
558
|
+
}});
|
|
559
|
+
console.log(file.path);
|
|
560
|
+
|
|
561
|
+
// Save file dialog
|
|
562
|
+
const savePath = await Aegis.dialog.save({{
|
|
563
|
+
title: 'Save as',
|
|
564
|
+
defaultName: 'document.txt'
|
|
565
|
+
}});
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
### Download with Progress
|
|
569
|
+
|
|
570
|
+
```javascript
|
|
571
|
+
// Download file with progress bar
|
|
572
|
+
await Aegis.download(
|
|
573
|
+
{{
|
|
574
|
+
url: 'https://example.com/file.zip',
|
|
575
|
+
dest: '/home/user/downloads/file.zip'
|
|
576
|
+
}},
|
|
577
|
+
(progress) => {{
|
|
578
|
+
const percent = progress.percent.toFixed(1);
|
|
579
|
+
progressBar.style.width = percent + '%';
|
|
580
|
+
statusText.textContent = `${{progress.downloaded}} / ${{progress.total}} bytes`;
|
|
581
|
+
}}
|
|
582
|
+
);
|
|
477
583
|
```
|
|
478
584
|
|
|
585
|
+
### App Control
|
|
586
|
+
|
|
587
|
+
```javascript
|
|
588
|
+
// Window controls
|
|
589
|
+
Aegis.app.minimize();
|
|
590
|
+
Aegis.app.maximize();
|
|
591
|
+
Aegis.app.quit();
|
|
592
|
+
|
|
593
|
+
// Get system paths (localized for your language!)
|
|
594
|
+
const home = await Aegis.app.getPath({{ name: 'home' }});
|
|
595
|
+
const docs = await Aegis.app.getPath({{ name: 'documents' }}); // Returns "Documentos" on pt-BR
|
|
596
|
+
const downloads = await Aegis.app.getPath({{ name: 'downloads' }});
|
|
597
|
+
// Also: desktop, music, pictures, videos
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
### Window Control (Frameless Windows)
|
|
601
|
+
|
|
602
|
+
```javascript
|
|
603
|
+
// Make element draggable for window movement
|
|
604
|
+
Aegis.window.moveBar('#titlebar', {{ exclude: '.btn-close' }});
|
|
605
|
+
|
|
606
|
+
// Setup resize handles
|
|
607
|
+
Aegis.window.resizeHandles({{
|
|
608
|
+
'.resize-n': 'n',
|
|
609
|
+
'.resize-s': 's',
|
|
610
|
+
'.resize-se': 'se',
|
|
611
|
+
// Options: n, s, e, w, ne, nw, se, sw
|
|
612
|
+
}});
|
|
613
|
+
|
|
614
|
+
// Get/set window size
|
|
615
|
+
const size = await Aegis.window.getSize();
|
|
616
|
+
await Aegis.window.setSize({{ width: 1024, height: 768 }});
|
|
617
|
+
|
|
618
|
+
// Get/set window position
|
|
619
|
+
const pos = await Aegis.window.getPosition();
|
|
620
|
+
await Aegis.window.setPosition({{ x: 100, y: 100 }});
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
## 🔒 Security (preload.js)
|
|
624
|
+
|
|
625
|
+
Control which APIs your app can access:
|
|
626
|
+
|
|
627
|
+
```javascript
|
|
628
|
+
// src/preload.js
|
|
629
|
+
Aegis.expose([
|
|
630
|
+
'read', // File reading
|
|
631
|
+
'write', // File writing
|
|
632
|
+
'run', // Command execution
|
|
633
|
+
'dialog', // Native dialogs
|
|
634
|
+
'app', // App control
|
|
635
|
+
'window', // Window control
|
|
636
|
+
'download', // Download with progress
|
|
637
|
+
'exists', // File existence
|
|
638
|
+
'mkdir', // Create directories
|
|
639
|
+
'remove', // Delete files
|
|
640
|
+
'copy', // Copy files
|
|
641
|
+
'move' // Move/rename files
|
|
642
|
+
]);
|
|
643
|
+
|
|
644
|
+
// For maximum security, only expose what you need!
|
|
645
|
+
// If you omit 'run', the app cannot execute shell commands
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
## ⚙️ Configuration (aegis.config.json)
|
|
649
|
+
|
|
650
|
+
```json
|
|
651
|
+
{{
|
|
652
|
+
"name": "{project_name}",
|
|
653
|
+
"title": "My Awesome App",
|
|
654
|
+
"version": "1.0.0",
|
|
655
|
+
"main": "src/index.html",
|
|
656
|
+
"preload": "src/preload.js",
|
|
657
|
+
"width": 1200,
|
|
658
|
+
"height": 800,
|
|
659
|
+
"resizable": true,
|
|
660
|
+
"frame": true,
|
|
661
|
+
"icon": "assets/icon.png"
|
|
662
|
+
}}
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
| Option | Description |
|
|
666
|
+
|--------|-------------|
|
|
667
|
+
| `frame` | Set to `false` for frameless window (custom titlebar) |
|
|
668
|
+
| `resizable` | Allow window resizing |
|
|
669
|
+
| `width/height` | Initial window size |
|
|
670
|
+
| `devTools` | Enable right-click → Inspect Element |
|
|
671
|
+
|
|
672
|
+
## 📦 Building AppImage
|
|
673
|
+
|
|
674
|
+
```bash
|
|
675
|
+
aegis build
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
This creates a portable AppImage (~200KB!) in the `dist/` folder.
|
|
679
|
+
|
|
680
|
+
**Note:** The AppImage requires `python3-gi` and `gir1.2-webkit2-4.1` on the target system.
|
|
681
|
+
|
|
682
|
+
## 🆚 Why Aegis over Electron?
|
|
683
|
+
|
|
684
|
+
| Aspect | Electron | Aegis |
|
|
685
|
+
|--------|----------|-------|
|
|
686
|
+
| App Size | ~150 MB | **~200 KB** |
|
|
687
|
+
| Backend | Node.js | Python |
|
|
688
|
+
| Renderer | Chromium (bundled) | WebKit2GTK (system) |
|
|
689
|
+
| RAM Usage | High (~100MB+) | Low (~30MB) |
|
|
690
|
+
| Platform | Cross-platform | Linux |
|
|
691
|
+
|
|
692
|
+
## 📚 Learn More
|
|
693
|
+
|
|
694
|
+
- [Aegis GitHub](https://github.com/Diegopam/aegis-framework)
|
|
695
|
+
- [npm Package](https://www.npmjs.com/package/aegis-framework)
|
|
696
|
+
|
|
479
697
|
## License
|
|
480
698
|
|
|
481
699
|
MIT
|
|
Binary file
|
|
Binary file
|
package/aegis/core/window.py
CHANGED
|
@@ -10,6 +10,9 @@ gi.require_version('WebKit2', '4.1')
|
|
|
10
10
|
from gi.repository import Gtk, WebKit2, Gdk, GLib
|
|
11
11
|
import json
|
|
12
12
|
import os
|
|
13
|
+
import threading
|
|
14
|
+
import urllib.request
|
|
15
|
+
import shutil
|
|
13
16
|
|
|
14
17
|
|
|
15
18
|
class AegisWindow(Gtk.Window):
|
|
@@ -114,12 +117,17 @@ class AegisWindow(Gtk.Window):
|
|
|
114
117
|
|
|
115
118
|
print(f"[Aegis] Action: {action}, Payload: {payload}")
|
|
116
119
|
|
|
117
|
-
#
|
|
118
|
-
|
|
120
|
+
# Check if this is an async action
|
|
121
|
+
async_actions = {'run.async', 'download', 'copy.async'}
|
|
119
122
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
self.
|
|
123
|
+
if action in async_actions:
|
|
124
|
+
# Handle in background thread - response sent via callback
|
|
125
|
+
self._process_async_action(action, payload, callback_id)
|
|
126
|
+
else:
|
|
127
|
+
# Sync action - process and respond immediately
|
|
128
|
+
result = self._process_action(action, payload)
|
|
129
|
+
if callback_id:
|
|
130
|
+
self._send_response(callback_id, result)
|
|
123
131
|
|
|
124
132
|
except Exception as e:
|
|
125
133
|
print(f"[Aegis Bridge] Error: {e}")
|
|
@@ -597,6 +605,180 @@ class AegisWindow(Gtk.Window):
|
|
|
597
605
|
)
|
|
598
606
|
self.content_manager.add_script(user_script)
|
|
599
607
|
|
|
608
|
+
# ==================== Async Action Handlers ====================
|
|
609
|
+
|
|
610
|
+
def _process_async_action(self, action, payload, callback_id):
|
|
611
|
+
"""Process async actions in background threads"""
|
|
612
|
+
handlers = {
|
|
613
|
+
'run.async': self._handle_run_async,
|
|
614
|
+
'download': self._handle_download,
|
|
615
|
+
'copy.async': self._handle_copy_async,
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
handler = handlers.get(action)
|
|
619
|
+
if handler:
|
|
620
|
+
# Start background thread
|
|
621
|
+
thread = threading.Thread(
|
|
622
|
+
target=handler,
|
|
623
|
+
args=(payload, callback_id),
|
|
624
|
+
daemon=True
|
|
625
|
+
)
|
|
626
|
+
thread.start()
|
|
627
|
+
else:
|
|
628
|
+
GLib.idle_add(self._send_error, callback_id, f"Unknown async action: {action}")
|
|
629
|
+
|
|
630
|
+
def _send_progress(self, callback_id, progress_data):
|
|
631
|
+
"""Send progress update to JavaScript (must be called via GLib.idle_add)"""
|
|
632
|
+
response = json.dumps({
|
|
633
|
+
'callbackId': callback_id,
|
|
634
|
+
'type': 'progress',
|
|
635
|
+
'data': progress_data
|
|
636
|
+
})
|
|
637
|
+
script = f"window.__aegisProgress({response})"
|
|
638
|
+
self.webview.evaluate_javascript(script, -1, None, None, None, None, None)
|
|
639
|
+
return False # Don't repeat
|
|
640
|
+
|
|
641
|
+
def _handle_run_async(self, payload, callback_id):
|
|
642
|
+
"""Execute command asynchronously with streaming output"""
|
|
643
|
+
import subprocess
|
|
644
|
+
|
|
645
|
+
try:
|
|
646
|
+
cmd = payload.get('sh', '')
|
|
647
|
+
|
|
648
|
+
process = subprocess.Popen(
|
|
649
|
+
cmd,
|
|
650
|
+
shell=True,
|
|
651
|
+
stdout=subprocess.PIPE,
|
|
652
|
+
stderr=subprocess.STDOUT,
|
|
653
|
+
text=True,
|
|
654
|
+
bufsize=1
|
|
655
|
+
)
|
|
656
|
+
|
|
657
|
+
output_lines = []
|
|
658
|
+
|
|
659
|
+
# Stream output line by line
|
|
660
|
+
for line in process.stdout:
|
|
661
|
+
output_lines.append(line)
|
|
662
|
+
GLib.idle_add(self._send_progress, callback_id, {
|
|
663
|
+
'type': 'output',
|
|
664
|
+
'line': line.rstrip('\n')
|
|
665
|
+
})
|
|
666
|
+
|
|
667
|
+
process.wait()
|
|
668
|
+
|
|
669
|
+
# Send final result
|
|
670
|
+
result = {
|
|
671
|
+
'output': ''.join(output_lines),
|
|
672
|
+
'exitCode': process.returncode
|
|
673
|
+
}
|
|
674
|
+
GLib.idle_add(self._send_response, callback_id, result)
|
|
675
|
+
|
|
676
|
+
except Exception as e:
|
|
677
|
+
GLib.idle_add(self._send_error, callback_id, str(e))
|
|
678
|
+
|
|
679
|
+
def _handle_download(self, payload, callback_id):
|
|
680
|
+
"""Download file with progress updates"""
|
|
681
|
+
try:
|
|
682
|
+
url = payload.get('url')
|
|
683
|
+
dest = payload.get('dest')
|
|
684
|
+
|
|
685
|
+
# Create request
|
|
686
|
+
req = urllib.request.Request(url, headers={
|
|
687
|
+
'User-Agent': 'Aegis/0.1.0'
|
|
688
|
+
})
|
|
689
|
+
|
|
690
|
+
response = urllib.request.urlopen(req, timeout=30)
|
|
691
|
+
total_size = int(response.headers.get('Content-Length', 0))
|
|
692
|
+
downloaded = 0
|
|
693
|
+
|
|
694
|
+
# Ensure destination directory exists
|
|
695
|
+
os.makedirs(os.path.dirname(dest) or '.', exist_ok=True)
|
|
696
|
+
|
|
697
|
+
with open(dest, 'wb') as f:
|
|
698
|
+
while True:
|
|
699
|
+
chunk = response.read(8192)
|
|
700
|
+
if not chunk:
|
|
701
|
+
break
|
|
702
|
+
|
|
703
|
+
f.write(chunk)
|
|
704
|
+
downloaded += len(chunk)
|
|
705
|
+
|
|
706
|
+
# Send progress update
|
|
707
|
+
progress = {
|
|
708
|
+
'downloaded': downloaded,
|
|
709
|
+
'total': total_size,
|
|
710
|
+
'percent': (downloaded / total_size * 100) if total_size else 0
|
|
711
|
+
}
|
|
712
|
+
GLib.idle_add(self._send_progress, callback_id, progress)
|
|
713
|
+
|
|
714
|
+
# Send completion
|
|
715
|
+
result = {
|
|
716
|
+
'success': True,
|
|
717
|
+
'path': dest,
|
|
718
|
+
'size': downloaded
|
|
719
|
+
}
|
|
720
|
+
GLib.idle_add(self._send_response, callback_id, result)
|
|
721
|
+
|
|
722
|
+
except Exception as e:
|
|
723
|
+
GLib.idle_add(self._send_error, callback_id, str(e))
|
|
724
|
+
|
|
725
|
+
def _handle_copy_async(self, payload, callback_id):
|
|
726
|
+
"""Copy files/directories with progress (for large files)"""
|
|
727
|
+
try:
|
|
728
|
+
src = payload.get('src')
|
|
729
|
+
dest = payload.get('dest')
|
|
730
|
+
|
|
731
|
+
if os.path.isdir(src):
|
|
732
|
+
# Copy directory
|
|
733
|
+
def copy_with_progress(src_dir, dest_dir):
|
|
734
|
+
total_files = sum([len(files) for _, _, files in os.walk(src_dir)])
|
|
735
|
+
copied = 0
|
|
736
|
+
|
|
737
|
+
for root, dirs, files in os.walk(src_dir):
|
|
738
|
+
rel_path = os.path.relpath(root, src_dir)
|
|
739
|
+
dest_path = os.path.join(dest_dir, rel_path)
|
|
740
|
+
os.makedirs(dest_path, exist_ok=True)
|
|
741
|
+
|
|
742
|
+
for file in files:
|
|
743
|
+
src_file = os.path.join(root, file)
|
|
744
|
+
dest_file = os.path.join(dest_path, file)
|
|
745
|
+
shutil.copy2(src_file, dest_file)
|
|
746
|
+
copied += 1
|
|
747
|
+
|
|
748
|
+
GLib.idle_add(self._send_progress, callback_id, {
|
|
749
|
+
'copied': copied,
|
|
750
|
+
'total': total_files,
|
|
751
|
+
'percent': (copied / total_files * 100) if total_files else 100,
|
|
752
|
+
'current': file
|
|
753
|
+
})
|
|
754
|
+
|
|
755
|
+
copy_with_progress(src, dest)
|
|
756
|
+
else:
|
|
757
|
+
# Copy single file with progress
|
|
758
|
+
file_size = os.path.getsize(src)
|
|
759
|
+
copied = 0
|
|
760
|
+
|
|
761
|
+
with open(src, 'rb') as fsrc:
|
|
762
|
+
with open(dest, 'wb') as fdest:
|
|
763
|
+
while True:
|
|
764
|
+
chunk = fsrc.read(8192)
|
|
765
|
+
if not chunk:
|
|
766
|
+
break
|
|
767
|
+
fdest.write(chunk)
|
|
768
|
+
copied += len(chunk)
|
|
769
|
+
|
|
770
|
+
GLib.idle_add(self._send_progress, callback_id, {
|
|
771
|
+
'copied': copied,
|
|
772
|
+
'total': file_size,
|
|
773
|
+
'percent': (copied / file_size * 100) if file_size else 100
|
|
774
|
+
})
|
|
775
|
+
|
|
776
|
+
result = {'success': True, 'src': src, 'dest': dest}
|
|
777
|
+
GLib.idle_add(self._send_response, callback_id, result)
|
|
778
|
+
|
|
779
|
+
except Exception as e:
|
|
780
|
+
GLib.idle_add(self._send_error, callback_id, str(e))
|
|
781
|
+
|
|
600
782
|
def run(self):
|
|
601
783
|
"""Show window and start main loop"""
|
|
602
784
|
self.show_all()
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
let callbackId = 0;
|
|
16
16
|
const pendingCallbacks = new Map();
|
|
17
|
+
const progressCallbacks = new Map(); // For async progress updates
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Resolve a callback from Python
|
|
@@ -22,6 +23,7 @@
|
|
|
22
23
|
const callback = pendingCallbacks.get(response.callbackId);
|
|
23
24
|
if (callback) {
|
|
24
25
|
pendingCallbacks.delete(response.callbackId);
|
|
26
|
+
progressCallbacks.delete(response.callbackId); // Cleanup progress callback
|
|
25
27
|
if (response.success) {
|
|
26
28
|
callback.resolve(response.data);
|
|
27
29
|
} else {
|
|
@@ -30,6 +32,16 @@
|
|
|
30
32
|
}
|
|
31
33
|
};
|
|
32
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Handle progress updates from Python (for async operations)
|
|
37
|
+
*/
|
|
38
|
+
window.__aegisProgress = function (response) {
|
|
39
|
+
const callback = progressCallbacks.get(response.callbackId);
|
|
40
|
+
if (callback) {
|
|
41
|
+
callback(response.data);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
33
45
|
/**
|
|
34
46
|
* Send a message to Python backend
|
|
35
47
|
*/
|
|
@@ -78,6 +90,41 @@
|
|
|
78
90
|
};
|
|
79
91
|
}
|
|
80
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Invoke an async action with progress callback
|
|
95
|
+
* @param {string} action - The action name
|
|
96
|
+
* @param {object} payload - The payload
|
|
97
|
+
* @param {function} onProgress - Progress callback function
|
|
98
|
+
*/
|
|
99
|
+
function invokeWithProgress(action, payload, onProgress) {
|
|
100
|
+
return new Promise((resolve, reject) => {
|
|
101
|
+
if (!isAllowed(action)) {
|
|
102
|
+
reject(new Error(`API '${action}' is not allowed by preload`));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const id = ++callbackId;
|
|
107
|
+
pendingCallbacks.set(id, { resolve, reject });
|
|
108
|
+
|
|
109
|
+
// Register progress callback if provided
|
|
110
|
+
if (onProgress && typeof onProgress === 'function') {
|
|
111
|
+
progressCallbacks.set(id, onProgress);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const message = JSON.stringify({
|
|
115
|
+
action: action,
|
|
116
|
+
payload: payload,
|
|
117
|
+
callbackId: id
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.aegis) {
|
|
121
|
+
window.webkit.messageHandlers.aegis.postMessage(message);
|
|
122
|
+
} else {
|
|
123
|
+
reject(new Error('Aegis bridge not available'));
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
81
128
|
// ==================== Public Aegis API ====================
|
|
82
129
|
|
|
83
130
|
const Aegis = {
|
|
@@ -170,6 +217,74 @@
|
|
|
170
217
|
*/
|
|
171
218
|
copy: guardedInvoke('copy'),
|
|
172
219
|
|
|
220
|
+
// ==================== Async Operations with Progress ====================
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Run command asynchronously with streaming output
|
|
224
|
+
*
|
|
225
|
+
* @param {object} options - Command options
|
|
226
|
+
* @param {string} options.sh - Shell command to execute
|
|
227
|
+
* @param {function} onProgress - Progress callback, called with each output line
|
|
228
|
+
* @returns {Promise<{output: string, exitCode: number}>}
|
|
229
|
+
*
|
|
230
|
+
* @example
|
|
231
|
+
* const result = await Aegis.runAsync(
|
|
232
|
+
* { sh: 'apt install htop' },
|
|
233
|
+
* (progress) => {
|
|
234
|
+
* console.log(progress.line); // Each line of output
|
|
235
|
+
* }
|
|
236
|
+
* );
|
|
237
|
+
*/
|
|
238
|
+
runAsync: function (options, onProgress) {
|
|
239
|
+
return invokeWithProgress('run.async', options, onProgress);
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Download file with progress updates
|
|
244
|
+
*
|
|
245
|
+
* @param {object} options - Download options
|
|
246
|
+
* @param {string} options.url - URL to download from
|
|
247
|
+
* @param {string} options.dest - Destination file path
|
|
248
|
+
* @param {function} onProgress - Progress callback
|
|
249
|
+
* @returns {Promise<{success: boolean, path: string, size: number}>}
|
|
250
|
+
*
|
|
251
|
+
* @example
|
|
252
|
+
* const result = await Aegis.download(
|
|
253
|
+
* {
|
|
254
|
+
* url: 'https://example.com/file.zip',
|
|
255
|
+
* dest: '/home/user/downloads/file.zip'
|
|
256
|
+
* },
|
|
257
|
+
* (progress) => {
|
|
258
|
+
* progressBar.style.width = progress.percent + '%';
|
|
259
|
+
* statusText.textContent = `${progress.downloaded} / ${progress.total}`;
|
|
260
|
+
* }
|
|
261
|
+
* );
|
|
262
|
+
*/
|
|
263
|
+
download: function (options, onProgress) {
|
|
264
|
+
return invokeWithProgress('download', options, onProgress);
|
|
265
|
+
},
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Copy file or directory with progress updates (for large files)
|
|
269
|
+
*
|
|
270
|
+
* @param {object} options - Copy options
|
|
271
|
+
* @param {string} options.src - Source path
|
|
272
|
+
* @param {string} options.dest - Destination path
|
|
273
|
+
* @param {function} onProgress - Progress callback
|
|
274
|
+
* @returns {Promise<{success: boolean, src: string, dest: string}>}
|
|
275
|
+
*
|
|
276
|
+
* @example
|
|
277
|
+
* await Aegis.copyAsync(
|
|
278
|
+
* { src: '/path/to/large-folder', dest: '/path/to/backup' },
|
|
279
|
+
* (progress) => {
|
|
280
|
+
* console.log(`${progress.percent.toFixed(1)}% - ${progress.current}`);
|
|
281
|
+
* }
|
|
282
|
+
* );
|
|
283
|
+
*/
|
|
284
|
+
copyAsync: function (options, onProgress) {
|
|
285
|
+
return invokeWithProgress('copy.async', options, onProgress);
|
|
286
|
+
},
|
|
287
|
+
|
|
173
288
|
/**
|
|
174
289
|
* Move/rename file or directory
|
|
175
290
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aegis-framework",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Lightweight AppImage framework using WebKit2GTK and Python - An alternative to Electron that creates ~200KB apps instead of 150MB!",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"appimage",
|
|
@@ -48,4 +48,4 @@
|
|
|
48
48
|
"engines": {
|
|
49
49
|
"node": ">=14"
|
|
50
50
|
}
|
|
51
|
-
}
|
|
51
|
+
}
|