Compare commits
106 Commits
master
...
b3f59b7bf5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3f59b7bf5 | ||
|
|
01797f28aa | ||
|
|
2af6b3954b | ||
|
|
5a5814b2bc | ||
|
|
0ebfcd18bb | ||
|
|
2ad6bce8db | ||
|
|
7c0bb6719d | ||
|
|
fb8af70742 | ||
|
|
3d7aa1ab92 | ||
|
|
7784f7f2a3 | ||
|
|
083fc56337 | ||
|
|
e942b234c4 | ||
|
|
f6875acc72 | ||
|
|
280de213c1 | ||
|
|
30db989b2c | ||
|
|
0497f5767b | ||
|
|
fdbd12b64f | ||
|
|
54ae460da5 | ||
| 9a7b2b4089 | |||
| 5e04deab9c | |||
|
|
99984e7b6c | ||
|
|
fb02fc80a0 | ||
|
|
bc4bf6d294 | ||
|
|
29111e8dce | ||
|
|
a25978cf6f | ||
|
|
6c7a81f915 | ||
|
|
9f9ad8b1bb | ||
|
|
75c5d289cf | ||
|
|
4870a56dad | ||
|
|
86feb8517a | ||
| 30b7a00229 | |||
|
|
ff342fd9ea | ||
|
|
ced234fa08 | ||
|
|
c37c87f6d4 | ||
|
|
cdae86dd86 | ||
|
|
14cc3d1c3e | ||
|
|
a54d2c08bc | ||
|
|
6e7dacc1e0 | ||
|
|
f3558dc75b | ||
|
|
a83334aac7 | ||
|
|
987642886c | ||
|
|
fb75d450ac | ||
|
|
0dce6c89c0 | ||
|
|
0028df4604 | ||
|
|
040ab4aa4e | ||
|
|
2d27750d0e | ||
|
|
adca2e511b | ||
|
|
eb53e46ebc | ||
|
|
3a4812de4c | ||
| 31203648b4 | |||
|
|
e91907a884 | ||
|
|
7ac3f1fb16 | ||
|
|
556f4e66a5 | ||
|
|
9094b28376 | ||
| 9b1fffe024 | |||
| 7745ac1ef0 | |||
| ddda53d643 | |||
| 328cd1d51a | |||
|
|
5105f42479 | ||
|
|
ab6a071986 | ||
|
|
d762ec3b20 | ||
|
|
2533410c78 | ||
|
|
60df29243c | ||
|
|
d67d0085ef | ||
|
|
8283a4b0f7 | ||
|
|
c97e84b0ef | ||
|
|
bc1a3b2cc5 | ||
|
|
f706855b5b | ||
|
|
38f5b39060 | ||
|
|
143a760128 | ||
|
|
976ca7e0c1 | ||
|
|
491574a438 | ||
|
|
abd0165164 | ||
|
|
bf97d7acf1 | ||
|
|
fd279a9635 | ||
|
|
7b4f39760c | ||
|
|
31b278f238 | ||
|
|
01d2e214cb | ||
|
|
d2f0eaa1dc | ||
|
|
f565e0b421 | ||
|
|
b4d8dfa756 | ||
|
|
4bc1e8c4c9 | ||
|
|
2a56129336 | ||
|
|
f07aed2a02 | ||
|
|
f6fab17e00 | ||
|
|
4c65d9dc42 | ||
|
|
98bd835342 | ||
| 6802eac1d9 | |||
| 0906248fd1 | |||
| 26b48c4fc1 | |||
| 1860f9c09e | |||
| 0b7921f660 | |||
|
|
8ec3eb0b6a | ||
|
|
42a119cf34 | ||
|
|
6a788c11b4 | ||
|
|
88d166a8d8 | ||
|
|
80b9c29cb1 | ||
|
|
c156852cb2 | ||
|
|
4aec075855 | ||
|
|
843755fc00 | ||
|
|
d17d475dba | ||
|
|
7c13beea0a | ||
|
|
69f87fad35 | ||
|
|
e0be599146 | ||
|
|
01746ec98d | ||
|
|
4b28c16996 |
11
.gitignore
vendored
11
.gitignore
vendored
@@ -8,7 +8,7 @@
|
||||
*.sock
|
||||
*.csv
|
||||
*.xlsx
|
||||
#*.pdf
|
||||
# *.pdf
|
||||
*.cfg
|
||||
*.ini
|
||||
*.bak
|
||||
@@ -20,7 +20,10 @@ backups/
|
||||
tmp/
|
||||
temp/
|
||||
development/
|
||||
.vscode
|
||||
*.code-workspace
|
||||
|
||||
# .vscode
|
||||
# *.code-workspace
|
||||
file_cache/
|
||||
aether_native_app_config.json
|
||||
# package-lock.json
|
||||
builds/
|
||||
resources/seed_config.json
|
||||
|
||||
236
README.md
Normal file
236
README.md
Normal file
@@ -0,0 +1,236 @@
|
||||
# Aether Native Launcher (Electron)
|
||||
|
||||
The Aether Native Launcher is a specialized Electron-based shell for the Aether Platform. It provides a secure bridge between the SvelteKit web UI and the local operating system, enabling features restricted by browser sandboxing.
|
||||
|
||||
## 🚀 Overview
|
||||
|
||||
This application serves as the "Native Mode" runtime for Aether podiums and devices. It handles:
|
||||
- **Local File Orchestration:** Managed cache for presentation files (PPTx, Keynote, PDF).
|
||||
- **Automation:** Specialized AppleScript handlers for PowerPoint and Keynote.
|
||||
- **Hardware Telemetry:** Direct access to CPU, RAM, and Network interface data.
|
||||
- **Remote Control:** Slide navigation and application control via WebSocket intents.
|
||||
|
||||
## 🖥️ Onsite Deployment
|
||||
|
||||
**Current hardware:** MacBook Air 2018 — Intel x64. All current deployments use `aether_launcher-darwin-x64`.
|
||||
**Future hardware:** Apple Silicon Macs use `aether_launcher-darwin-arm64`. Windows and Linux builds are planned.
|
||||
|
||||
SSH user on all laptops: **`speaker ready`**
|
||||
IP pattern: `192.168.32.1XX` (XX = zero-padded laptop number, e.g. 03 → `.103`). Find/replace this prefix for other onsite environments.
|
||||
|
||||
### Step 1 — Build the app (workstation)
|
||||
|
||||
```bash
|
||||
cd ~/OSIT_dev/aether_app_native_electron
|
||||
npm run package:mac
|
||||
# Produces builds/aether_launcher-darwin-x64/aether_launcher.app ← the one to deploy
|
||||
```
|
||||
|
||||
Only rebuild if source code has changed. The `.app` bundle is identical for all Intel laptops —
|
||||
only `~/seed.json` differs per device.
|
||||
|
||||
### Step 2 — Determine target architecture and copy the .app
|
||||
|
||||
Check the target Mac's CPU if unsure:
|
||||
```bash
|
||||
ssh "speaker ready"@192.168.32.103 "uname -m"
|
||||
# x86_64 → use aether_launcher-darwin-x64 (MacBook Air 2018 and all current Intel Macs)
|
||||
# arm64 → use aether_launcher-darwin-arm64 (Apple Silicon M1/M2/M3/M4)
|
||||
```
|
||||
|
||||
Copy from the workstation (replace `103` with the target IP last octet):
|
||||
```bash
|
||||
# Intel (current hardware):
|
||||
scp -r builds/aether_launcher-darwin-x64/aether_launcher.app \
|
||||
"speaker ready"@192.168.32.103:/Applications/aether_launcher.app
|
||||
|
||||
# Apple Silicon (future hardware):
|
||||
scp -r builds/aether_launcher-darwin-arm64/aether_launcher.app \
|
||||
"speaker ready"@192.168.32.103:/Applications/aether_launcher.app
|
||||
```
|
||||
|
||||
If `/Applications/aether_launcher.app` already exists, `scp -r` overwrites it. No need to
|
||||
remove the old version first.
|
||||
|
||||
### Step 3 — Write seed.json on the target laptop
|
||||
|
||||
The seed file lives at `~/seed.json` (`/Users/speaker ready/seed.json`) on each Mac.
|
||||
It is intentionally outside the app bundle so it can be updated without redeploying.
|
||||
|
||||
```bash
|
||||
ssh "speaker ready"@192.168.32.103 "cat > ~/seed.json" << 'EOF'
|
||||
{
|
||||
"event_device_id": "DEVICE_ID_FOR_THIS_LAPTOP",
|
||||
"aether_api_key": "YOUR_API_KEY",
|
||||
"primary_api_base_url": "https://api.oneskyit.com",
|
||||
"backup_api_base_url": "https://bak-api.oneskyit.com",
|
||||
"onsite_api_base_url": null
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
`event_device_id` values by laptop — see the **Device Reference** table below.
|
||||
|
||||
### Step 4 — Verify
|
||||
|
||||
```bash
|
||||
# Confirm seed.json landed correctly
|
||||
ssh "speaker ready"@192.168.32.103 "cat ~/seed.json"
|
||||
|
||||
# Confirm the .app is present
|
||||
ssh "speaker ready"@192.168.32.103 "ls /Applications/aether_launcher.app"
|
||||
```
|
||||
|
||||
Then launch the app on the laptop and confirm it connects and shows the correct device name
|
||||
in the Launcher UI.
|
||||
|
||||
### Updating seed.json only (no app reinstall)
|
||||
|
||||
If only the device config needs updating (e.g. changing `event_device_id` or `onsite_api_base_url`):
|
||||
|
||||
```bash
|
||||
ssh "speaker ready"@192.168.32.103 "cat > ~/seed.json" << 'EOF'
|
||||
{ ... }
|
||||
EOF
|
||||
```
|
||||
|
||||
No need to re-copy the `.app`. Restart the Electron app after writing the new seed.
|
||||
|
||||
### Deploy to multiple laptops at once
|
||||
|
||||
Repeat Steps 2–3 for each laptop, or use a loop:
|
||||
|
||||
```bash
|
||||
for OCTET in 103 104 105 106; do
|
||||
echo "=== Deploying to 192.168.32.$OCTET ==="
|
||||
scp -r builds/aether_launcher-darwin-x64/aether_launcher.app \
|
||||
"speaker ready"@192.168.32.$OCTET:/Applications/aether_launcher.app
|
||||
done
|
||||
```
|
||||
|
||||
`seed.json` must still be written per-device (each has a unique `event_device_id`).
|
||||
|
||||
### Adding SSH key to a new laptop (first time only)
|
||||
|
||||
```bash
|
||||
ssh-copy-id "speaker ready"@192.168.32.1XX
|
||||
```
|
||||
|
||||
Run once per laptop before attempting any of the deploy steps above.
|
||||
|
||||
---
|
||||
|
||||
## 📋 Device Reference
|
||||
|
||||
| Laptop | IP Address | event_device_id | Notes |
|
||||
|--------|------------------|-----------------|--------------------------|
|
||||
| 01 | 192.168.32.101 | tFLL1fLQfnk | |
|
||||
| 02 | 192.168.32.102 | rpbfunVPEzw | |
|
||||
| 03 | 192.168.32.103 | 1EPfPX8kfw8 | |
|
||||
| 04 | 192.168.32.104 | zvgyLM5yieU | |
|
||||
| 05 | 192.168.32.105 | QOc046GoeSc | |
|
||||
| 06 | 192.168.32.106 | 2o8j6eb0L6s | |
|
||||
| 07 | 192.168.32.107 | Oa1tlxPEVSQ | |
|
||||
| 08 | 192.168.32.108 | fY4yznpUZ48 | |
|
||||
| 09 | 192.168.32.109 | YlgGCyjo9bY | |
|
||||
| 10 | 192.168.32.110 | GcTnFsp1mHI | |
|
||||
| 11 | 192.168.32.111 | 6z88m9oEZio | |
|
||||
| 12 | 192.168.32.112 | EggJqL2kWkA | |
|
||||
| 13 | 192.168.32.113 | O11eckHFdVE | |
|
||||
| 14 | 192.168.32.114 | reI0SecUEhI | |
|
||||
| 15 | 192.168.32.115 | crozxT8mA44 | |
|
||||
| 16 | 192.168.32.116 | 0nP4VZsvr2Q | |
|
||||
| 17 | 192.168.32.117 | Gm2gNqPGzLA | |
|
||||
| 19 | 192.168.32.119 | 6tpukvRVugU | (no laptop 18) |
|
||||
| x20 | 192.168.32.120 | rwLYnKUNd1M | old 04, spare/retired |
|
||||
|
||||
`aether_api_key`: all laptops share a single key per event deployment. The key is created in
|
||||
the Aether admin panel before the show and deleted after. Check `builds/seed.json` for the
|
||||
current key, or create a new one in Aether (Core → Accounts or Events → Devices API key section)
|
||||
before each deployment.
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Configuration
|
||||
|
||||
The application requires a `seed.json` file to identify the device and connect to the Aether API.
|
||||
|
||||
### 1. Seed Configuration
|
||||
|
||||
**Location: `~/seed.json`** (user's home directory — external to the app bundle by design)
|
||||
|
||||
This file is intentionally kept outside the application bundle so it can be edited per-device
|
||||
without re-signing or repackaging the app. On macOS this is `/Users/speaker ready/seed.json`.
|
||||
|
||||
Seed file format:
|
||||
```json
|
||||
{
|
||||
"event_device_id": "tFLL1fLQfnk",
|
||||
"aether_api_key": "YOUR_API_KEY",
|
||||
"primary_api_base_url": "https://api.oneskyit.com",
|
||||
"backup_api_base_url": "https://bak-api.oneskyit.com",
|
||||
"onsite_api_base_url": null
|
||||
}
|
||||
```
|
||||
|
||||
`event_device_id` is the `id_random` from the Aether `event_device` record for that physical
|
||||
laptop — see the Device Reference table above. `aether_api_key` is a shared key created per
|
||||
event deployment and deleted after the show.
|
||||
|
||||
### 2. Development Setup
|
||||
```bash
|
||||
npm install
|
||||
npm start # Compiles TypeScript (tsc) then launches Electron
|
||||
```
|
||||
|
||||
### 3. File Cache Layout
|
||||
|
||||
Presentation files are cached locally under `hash_prefix_length`-char subdirectories (default: 2):
|
||||
```text
|
||||
[local_file_cache_path]/
|
||||
4a/
|
||||
4a228ef8ac1a...sha256hash...file
|
||||
1d/
|
||||
1d720916a831...sha256hash...file
|
||||
```
|
||||
|
||||
**Important:** `hash_prefix_length` must be consistent. If it changes, files in old directories
|
||||
become orphaned and will be re-downloaded. The default is `2` and should not be changed unless
|
||||
explicitly coordinated across all devices.
|
||||
|
||||
## 🌉 The Native Bridge (`aetherNative`)
|
||||
|
||||
The bridge is exposed to the renderer via `contextBridge`. It can be accessed in the web UI via `window.aetherNative`.
|
||||
|
||||
### Core Methods
|
||||
|
||||
| Method | Description |
|
||||
| --- | --- |
|
||||
| `list_tools()` | Returns a JSON manifest of all available native functions. |
|
||||
| `launch_presentation({path, app})` | Launches a presentation with auto-focus and slideshow start. |
|
||||
| `control_presentation({app, action})` | Sends `next`, `prev`, `start`, or `stop` to active decks. |
|
||||
| `open_folder(path)` | Opens a local directory in the OS file explorer. |
|
||||
| `get_device_info()` | Returns hardware metadata (RAM, IPs, Hostname). |
|
||||
|
||||
### Example Usage (UI Relay)
|
||||
```typescript
|
||||
import * as native from '$lib/electron/electron_relay';
|
||||
|
||||
// Launch a file from local cache
|
||||
await native.launch_presentation({
|
||||
path: '[tmp]/my_deck.pptx',
|
||||
app: 'powerpoint'
|
||||
});
|
||||
|
||||
// Navigate slides
|
||||
await native.control_presentation({
|
||||
app: 'powerpoint',
|
||||
action: 'next'
|
||||
});
|
||||
```
|
||||
|
||||
## 🛠️ Development
|
||||
|
||||
- **Preload:** Logic defined in `src/preload/index.ts`.
|
||||
- **Handlers:** OS-level logic in `src/main/shell_handlers.ts` and `src/main/file_handlers.ts`.
|
||||
- **Types:** Shared TypeScript interfaces in `src/shared/types.ts`.
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
],
|
||||
"settings": {}
|
||||
}
|
||||
16
aether_app_native_electron.code-workspace
Normal file
16
aether_app_native_electron.code-workspace
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"git.autofetch": true,
|
||||
"markdownlint.config": {
|
||||
"MD007": false,
|
||||
"MD030": false,
|
||||
"MD004": false,
|
||||
"MD033": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
[Dolphin]
|
||||
Timestamp=2020,1,24,15,51,19
|
||||
Version=4
|
||||
ViewMode=2
|
||||
@@ -1,28 +0,0 @@
|
||||
body {
|
||||
/* min-height: 100%;
|
||||
height: 100%;
|
||||
max-height: 100%; */
|
||||
|
||||
margin: .1em;
|
||||
padding: .1em;
|
||||
}
|
||||
|
||||
section#Main-Body {
|
||||
/* outline: solid thin red; */
|
||||
|
||||
/* min-height: 100%;
|
||||
height: 100%;
|
||||
max-height: 100%; */
|
||||
}
|
||||
|
||||
section#Main-Nav-Menu {
|
||||
min-height: 100%;
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
section#Main-Content {
|
||||
min-height: 100%;
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.6 KiB |
@@ -1,497 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
||||
<title>One Sky IT</title>
|
||||
|
||||
<link rel="shortcut icon" type="image/png" href="img/favicon.ico">
|
||||
|
||||
<!-- Cascading Style Sheets (CSS) start -->
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
|
||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,400;0,700;1,400;1,700&display=swap" rel="stylesheet">
|
||||
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"> -->
|
||||
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" integrity="sha512-1ycn6IcaQQ40/MKBW2W4Rhis/DbILU74C1vSrLJxCq57o941Ym01SwNsOMqvEBFlcgUa6xLiPY/NS5R+E6ztJQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||
|
||||
<!-- One Sky IT default custom Cascading Style Sheets (CSS) -->
|
||||
|
||||
<!-- One Sky IT site custom Cascading Style Sheets (CSS) -->
|
||||
<link rel="stylesheet" href="css/native_app.css">
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<!-- Cascading Style Sheets (CSS) end -->
|
||||
|
||||
<!-- JavaScript (JS) start -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.22.0/axios.min.js" integrity="sha512-m2ssMAtdCEYGWXQ8hXVG4Q39uKYtbfaJL5QMTbhl2kc6vYyubrKHhr6aLLXW4ITeXSywQLn1AhsAaqrJl8Acfg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
|
||||
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.2.0/socket.io.js" integrity="sha512-WL6WGKMPBiM9PnHRYIn5YEtq0Z8XP4fkVb4qy7PP4vhmYQErJ/dySyXuFIMDf1eEYCXCrQrMJfkNwKc9gsjTjA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.10.7/dayjs.min.js" integrity="sha512-bwD3VD/j6ypSSnyjuaURidZksoVx3L1RPvTkleC48SbHCZsemT3VKMD39KknPnH728LLXVMTisESIBOAb5/W0Q==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
|
||||
<script src="https://cdn.rawgit.com/mozilla/localForage/master/dist/localforage.js"></script>
|
||||
|
||||
<!-- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script> -->
|
||||
|
||||
<!-- JavaScript (JS) end -->
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<main id="main_content" class="svelte_target event_launcher_main">
|
||||
</main>
|
||||
|
||||
|
||||
<!-- JavaScript (JS) start -->
|
||||
<!--
|
||||
Using node.js may require this: npm install jquery, add script below.
|
||||
https://stackoverflow.com/questions/22658015/bootstrap-throws-uncaught-error-bootstraps-javascript-requires-jquery
|
||||
-->
|
||||
<!--
|
||||
<script>
|
||||
window.jQuery = window.$ = require('jquery');
|
||||
</script>
|
||||
-->
|
||||
|
||||
<!-- One Sky IT default custom JavaScript (JS) -->
|
||||
|
||||
<!-- One Sky IT site custom JavaScript (JS) -->
|
||||
<script>const app = require('./js/module_app');</script>
|
||||
<script src="js/app_api.js"></script>
|
||||
<script src="js/app_idb.js"></script>
|
||||
<script src="js/app_ui_misc.js"></script>
|
||||
<script src="js/app_ui_sessions.js"></script>
|
||||
<script src="js/app_ui_presentations.js"></script>
|
||||
<script src="js/app_ui_presenters.js"></script>
|
||||
<script src="js/app_ui_files.js"></script>
|
||||
|
||||
<script>
|
||||
/* ***** **** *** ** * ### * ** *** **** ***** */
|
||||
|
||||
|
||||
let app_config = app.load_config();
|
||||
let account_id = app_config.account_id;
|
||||
let event_id = app_config.event_id;
|
||||
let event_location_id = app_config.event_location_id;
|
||||
|
||||
|
||||
/* ***** **** *** ** * ### * ** *** **** ***** */
|
||||
|
||||
// BEGIN: Set display options
|
||||
|
||||
const display_menu_session_times = app_config.display_menu_session_times;
|
||||
const display_session_codes = app_config.display_session_codes;
|
||||
const display_session_badges = app_config.display_session_badges;
|
||||
const display_presentation_codes = app_config.display_presentation_codes; // NOTE: not currently used
|
||||
const display_presentation_badges = app_config.display_presentation_badges;
|
||||
const display_presenter_codes = app_config.display_presenter_codes; // NOTE: not currently used
|
||||
const display_presenter_badges = app_config.display_presenter_badges; // NOTE: not currently used
|
||||
|
||||
|
||||
// END: Set display options
|
||||
|
||||
/* ***** **** *** ** * ### * ** *** **** ***** */
|
||||
|
||||
// BEGIN: API section
|
||||
|
||||
|
||||
const api_token_loop_interval = app_config.api_token_loop_interval;
|
||||
let api_update_period = app_config.idb_event_location_check_period;
|
||||
let api_update_datetime = Date.now();
|
||||
let waiting_on_api_token = false;
|
||||
|
||||
const api_secret_key = app_config.api_secret_key;
|
||||
let api_temporary_token = null;
|
||||
|
||||
let api_base_url = null; // 'http://api.localhost:5001' 'https://api.oneskyit.com'
|
||||
|
||||
if (app_config.use_local_api) {
|
||||
api_base_url = app_config.api_local_base_url; // 'http://api.localhost:5001'
|
||||
} else {
|
||||
api_base_url = app_config.api_remote_base_url; // 'https://api.oneskyit.com'
|
||||
}
|
||||
|
||||
axios.defaults.baseURL = api_base_url;
|
||||
axios.defaults.headers.post['Access-Control-Allow-Origin'] = app_config.access_control_allow_origin; // '*';
|
||||
|
||||
// Axios needs to authenticate first
|
||||
let axios_headers_set = false;
|
||||
|
||||
let app_online = false;
|
||||
//let app_use_cached_data = true;
|
||||
window.addEventListener('online', app.currently_online);
|
||||
window.addEventListener('offline', app.currently_offline);
|
||||
|
||||
var api_token_loop = setInterval(async function() {
|
||||
//console.log('****************** LOOP: API Token Loop ******************');
|
||||
|
||||
if (app_online && api_update_datetime < Date.now()) {
|
||||
//let api_temporary_token_result = api_token_request_async(axios, api_secret_key)
|
||||
api_temporary_token = await api_token_request_async(axios, api_secret_key)
|
||||
.then(function (result) {
|
||||
if (result) {
|
||||
console.log('Setting temporary API token to axios Authorization header: '+result);
|
||||
axios.defaults.headers.common['Authorization'] = `Token ${result}`;
|
||||
//api_temporary_token = result;
|
||||
|
||||
console.log('Axios headers have been set.');
|
||||
axios_headers_set = true;
|
||||
api_update_datetime = Date.now() + api_update_period;
|
||||
return result;
|
||||
} else {
|
||||
console.log('The temporary API token appears to have failed. Are we actually online? Setting the app to offline mode.');
|
||||
console.log(navigator.onLine);
|
||||
app.currently_offline();
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log('Something went wrong while trying to request a temporary API token.');
|
||||
console.log(error);
|
||||
return false;
|
||||
});
|
||||
} else if (app_online) {
|
||||
//console.log('Currently online, but not asking for a new temporary token yet.');
|
||||
} else {
|
||||
console.log('Currently offline. Can not request a new temporary API token.');
|
||||
}
|
||||
}, api_token_loop_interval);
|
||||
|
||||
|
||||
// Placing this online status check after the api_token_loop just in case?
|
||||
if (navigator.onLine) {
|
||||
app.currently_online();
|
||||
} else {
|
||||
app.currently_offline();
|
||||
}
|
||||
|
||||
|
||||
// END: API section
|
||||
|
||||
/* ***** **** *** ** * ### * ** *** **** ***** */
|
||||
|
||||
// BEGIN: Load IDB section
|
||||
|
||||
|
||||
let idb_name = app_config.idb_name;
|
||||
|
||||
var tbl_event = null;
|
||||
var tbl_event_location = null;
|
||||
var tbl_event_session = null;
|
||||
var tbl_event_presentation = null;
|
||||
var tbl_event_presenter = null;
|
||||
var tbl_event_file = null;
|
||||
var tbl_hosted_file = null;
|
||||
|
||||
var tbl_event_key_count = null;
|
||||
var tbl_event_location_key_count = null;
|
||||
var tbl_event_session_key_count = null;
|
||||
var tbl_event_presentation_key_count = null;
|
||||
var tbl_event_presenter_key_count = null;
|
||||
var tbl_event_file_key_count = null;
|
||||
var tbl_hosted_file_key_count = null;
|
||||
|
||||
//const open_tables_loop_interval = 10; // only runs briefly on start up
|
||||
|
||||
const update_idb_loop_interval = app_config.update_idb_loop_interval; // loop forever
|
||||
//const update_render_loop_interval = 10000; // loop forever; only run when the launcher object is safe
|
||||
|
||||
// IDB tables need to be opened
|
||||
let idb_tables_opened = false;
|
||||
|
||||
// check periods and datetimes for update_idb_loop
|
||||
let idb_event_check_period = app_config.idb_event_check_period;
|
||||
let idb_event_check_datetime = Date.now();
|
||||
let idb_event_location_check_period = app_config.idb_event_location_check_period;
|
||||
let idb_event_location_check_datetime = Date.now();
|
||||
let idb_event_session_check_period = app_config.idb_event_session_check_period;
|
||||
let idb_event_session_check_datetime = Date.now();
|
||||
let idb_event_presentation_check_period = app_config.idb_event_presentation_check_period;
|
||||
let idb_event_presentation_check_datetime = Date.now();
|
||||
let idb_event_presenter_check_period = app_config.idb_event_presenter_check_period;
|
||||
let idb_event_presenter_check_datetime = Date.now();
|
||||
let idb_event_file_check_period = app_config.idb_event_file_check_period;
|
||||
let idb_event_file_check_datetime = Date.now();
|
||||
|
||||
|
||||
//let run_update_idb = false;
|
||||
let run_check_file_cache = false;
|
||||
|
||||
let looping_tbl_event = false;
|
||||
let looping_tbl_event_location = false;
|
||||
let looping_tbl_event_session = false;
|
||||
let looping_tbl_event_presentation = false;
|
||||
let looping_tbl_event_presenter = false;
|
||||
let looping_tbl_event_file = false;
|
||||
|
||||
//let idb_tables_have_records = false;
|
||||
|
||||
if (window.indexedDB) {
|
||||
console.log('IndexedDB is supported');
|
||||
|
||||
let load_idb_tables_result = load_idb_tables()
|
||||
.then(async function (result) {
|
||||
console.log('IDB tables have been opened');
|
||||
idb_tables_opened = load_idb_tables_result;
|
||||
|
||||
console.log('Attempting to use cached IDB data...');
|
||||
|
||||
if (tbl_event_key_count
|
||||
&& tbl_event_location_key_count
|
||||
&& tbl_event_session_key_count
|
||||
&& tbl_event_presentation_key_count
|
||||
&& tbl_event_presenter_key_count
|
||||
&& tbl_event_file_key_count) {
|
||||
let render_event_records_result = render_event_records();
|
||||
let render_event_location_records_result = render_event_location_records();
|
||||
let render_event_session_records_result = render_event_session_records()
|
||||
.then(async function (result) {
|
||||
console.log('Rendering session records has finished. Moving to other records.');
|
||||
console.log(result);
|
||||
let render_event_presentation_records_result = await render_event_presentation_records();
|
||||
let render_event_presenter_records_result = await render_event_presenter_records();
|
||||
let render_event_file_records_result = await render_event_file_records();
|
||||
|
||||
return true;
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log('Something went wrong rendering session records.');
|
||||
console.log(error);
|
||||
return false;
|
||||
});
|
||||
} else {
|
||||
console.log('One or more of the IDB tables has 0 records. The IDB probably needs to be updated.');
|
||||
return true;
|
||||
}
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log('Something went wrong opening the IDB tables.');
|
||||
console.log(error);
|
||||
return false;
|
||||
});
|
||||
|
||||
} else {
|
||||
console.log('IndexedDB is NOT supported');
|
||||
close();
|
||||
}
|
||||
|
||||
|
||||
// END: Load IDB section
|
||||
|
||||
/* ***** **** *** ** * ### * ** *** **** ***** */
|
||||
|
||||
// BEGIN: Update IDB section
|
||||
|
||||
|
||||
var update_idb_loop = setInterval(async function() {
|
||||
//console.log('****************** LOOP: Update IDB ******************');
|
||||
|
||||
if (app_online && axios_headers_set && idb_tables_opened) {
|
||||
if (idb_event_check_datetime < Date.now()) {
|
||||
console.log('**** *** ** * Time to check events * ** *** ****');
|
||||
let v2_account_events = await v2_get_account_events(axios, account_id)
|
||||
.then(function (response) {
|
||||
for (var i in response) {
|
||||
tbl_event.setItem(response[i].id.toString(), response[i]);
|
||||
}
|
||||
idb_event_check_datetime = Date.now() + idb_event_check_period;
|
||||
let render_event_records_result = render_event_records(); // NOTE: v2 idb to ui
|
||||
//update_event_ui = true;
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log('Trying to check events. Something went wrong. Expired token?');
|
||||
console.log(error);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
if (idb_event_location_check_datetime < Date.now()) {
|
||||
console.log('**** *** ** * Time to check event locations * ** *** ****');
|
||||
let v2_event_locations = await v2_get_event_locations(axios, event_id);
|
||||
for (var i in v2_event_locations) {
|
||||
tbl_event_location.setItem(v2_event_locations[i].id.toString(), v2_event_locations[i]);
|
||||
}
|
||||
idb_event_location_check_datetime = Date.now() + idb_event_location_check_period;
|
||||
let render_event_location_records_result = render_event_location_records(); // NOTE: v2 idb to ui
|
||||
//update_event_location_ui = true;
|
||||
}
|
||||
|
||||
if (idb_event_session_check_datetime < Date.now()) {
|
||||
console.log('**** *** ** * Time to check event sessions * ** *** ****'); // All event sessions, not just location specific.
|
||||
let v2_event_sessions = await v2_get_event_sessions(axios, event_id); // Was v2_get_event_location_sessions
|
||||
//tbl_event_session.clear();
|
||||
for (var i in v2_event_sessions) {
|
||||
tbl_event_session.setItem(v2_event_sessions[i].id.toString(), v2_event_sessions[i]);
|
||||
}
|
||||
idb_event_session_check_datetime = Date.now() + idb_event_session_check_period;
|
||||
let render_event_session_records_result = render_event_session_records(); // NOTE: v2 idb to ui
|
||||
//update_event_session_ui = true;
|
||||
}
|
||||
|
||||
if (idb_event_presentation_check_datetime < Date.now()) {
|
||||
console.log('**** *** ** * Time to check event location presentations * ** *** ****');
|
||||
let v2_event_presentations = await v2_get_event_location_presentations(axios, event_location_id);
|
||||
for (var i in v2_event_presentations) {
|
||||
tbl_event_presentation.setItem(v2_event_presentations[i].id.toString(), v2_event_presentations[i]);
|
||||
}
|
||||
idb_event_presentation_check_datetime = Date.now() + idb_event_presentation_check_period;
|
||||
let render_event_presentation_records_result = await render_event_presentation_records(); // NOTE: v2 idb to ui
|
||||
//update_event_presentation_ui = true;
|
||||
}
|
||||
|
||||
if (idb_event_presenter_check_datetime < Date.now()) {
|
||||
console.log('**** *** ** * Time to check event location presenters * ** *** ****');
|
||||
let v2_event_presenters = await v2_get_event_location_presenters(axios, event_location_id);
|
||||
for (var i in v2_event_presenters) {
|
||||
tbl_event_presenter.setItem(v2_event_presenters[i].id.toString(), v2_event_presenters[i]);
|
||||
}
|
||||
idb_event_presenter_check_datetime = Date.now() + idb_event_presenter_check_period;
|
||||
|
||||
let render_event_presenter_records_result = await render_event_presenter_records(); // NOTE: v2 idb to ui
|
||||
//update_event_presenter_ui = true;
|
||||
}
|
||||
|
||||
if (idb_event_file_check_datetime < Date.now()) {
|
||||
console.log('**** *** ** * Time to check event location files * ** *** ****');
|
||||
|
||||
let v2_event_files = await v2_get_event_files(axios, event_id);
|
||||
for (var i in v2_event_files) {
|
||||
tbl_event_file.setItem(v2_event_files[i].id.toString(), v2_event_files[i]);
|
||||
}
|
||||
|
||||
let v2_event_location_files = await v2_get_event_location_files(axios, event_location_id);
|
||||
for (var i in v2_event_location_files) {
|
||||
tbl_event_file.setItem(v2_event_location_files[i].id.toString(), v2_event_location_files[i]);
|
||||
}
|
||||
|
||||
let v2_event_location_files_sessions = await v2_get_event_location_files_sessions(axios, event_location_id);
|
||||
for (var i in v2_event_location_files_sessions) {
|
||||
tbl_event_file.setItem(v2_event_location_files_sessions[i].id.toString(), v2_event_location_files_sessions[i]);
|
||||
}
|
||||
|
||||
let v2_event_location_files_presentations = await v2_get_event_location_files_presentations(axios, event_location_id);
|
||||
for (var i in v2_event_location_files_presentations) {
|
||||
tbl_event_file.setItem(v2_event_location_files_presentations[i].id.toString(), v2_event_location_files_presentations[i]);
|
||||
}
|
||||
|
||||
let v2_event_location_files_presenters = await v2_get_event_location_files_presenters(axios, event_location_id);
|
||||
for (var i in v2_event_location_files_presenters) {
|
||||
tbl_event_file.setItem(v2_event_location_files_presenters[i].id.toString(), v2_event_location_files_presenters[i]);
|
||||
}
|
||||
|
||||
//run_check_file_cache = true; // Set to true so that the local file cache will be checked against the updated event files
|
||||
idb_event_file_check_datetime = Date.now() + idb_event_file_check_period;
|
||||
|
||||
let render_event_file_records_result = await render_event_file_records(); // NOTE: v2 idb to ui
|
||||
|
||||
let check_file_cache_result = app.check_file_cache()
|
||||
.then(function (result) {
|
||||
if (result) {
|
||||
console.log('Cached files have now been updated.');
|
||||
} else {
|
||||
console.log('Cached files were not updated.');
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
//update_event_file_ui = true;
|
||||
}
|
||||
|
||||
} else if (app_online && idb_tables_opened && !axios_headers_set) {
|
||||
console.log('Online and waiting for the Axios headers to be set.');
|
||||
} else if (app_online && axios_headers_set && !idb_tables_opened) {
|
||||
console.log('Online and Axios headers are set. Waiting for the IDB tables to be opened');
|
||||
} else if (!app_online) {
|
||||
console.log('Waiting to update IDB once online.');
|
||||
} else {
|
||||
console.log('This should not happen.');
|
||||
}
|
||||
}, update_idb_loop_interval);
|
||||
|
||||
|
||||
// END: IDB section
|
||||
|
||||
/* ***** **** *** ** * ### * ** *** **** ***** */
|
||||
|
||||
// BEGIN: Cached file section
|
||||
|
||||
|
||||
const check_file_cache_loop_interval = app_config.check_file_cache_loop_interval; // loop forever; only run after the event_file table has updated
|
||||
//const update_render_loop_interval = app_config.update_render_loop_interval; // loop forever
|
||||
const host_file_cache_path = app_config.host_file_cache_path; // 'file_cache/'
|
||||
|
||||
var check_file_cache_loop = setInterval(async function() {
|
||||
//console.log('****************** LOOP: Check File Cache ******************');
|
||||
|
||||
if (run_check_file_cache) {
|
||||
console.log('FLAG: check_file_cache='+run_check_file_cache);
|
||||
check_file_cache_result = app.check_file_cache();
|
||||
|
||||
check_file_cache_result.then(function (response) {
|
||||
console.log('Cached files have now been updated.');
|
||||
});
|
||||
run_check_file_cache = false;
|
||||
} else {
|
||||
//console.log('Flag to run check_file_cache is set to false');
|
||||
}
|
||||
}, check_file_cache_loop_interval);
|
||||
|
||||
|
||||
// END: Cached file section
|
||||
|
||||
/* ***** **** *** ** * ### * ** *** **** ***** */
|
||||
|
||||
// BEGIN: Check online status section
|
||||
|
||||
|
||||
// NOTE: This might be needed because the navigator.onLine status is not always correct.
|
||||
var check_initial_online_status = setInterval(async function() {
|
||||
//console.log('****************** LOOP: Check Online Status ******************');
|
||||
//console.log('Secondary online status check?');
|
||||
//console.log(navigator.onLine);
|
||||
//clearInterval(check_initial_online_status);
|
||||
/*
|
||||
if (navigator.onLine) {
|
||||
//app.currently_online();
|
||||
} else {
|
||||
//app.currently_offline();
|
||||
}
|
||||
*/
|
||||
}, 3000);
|
||||
|
||||
|
||||
// END: Check online status section
|
||||
|
||||
/* ***** **** *** ** * ### * ** *** **** ***** */
|
||||
|
||||
// BEGIN: Status clock section
|
||||
|
||||
|
||||
let status_clock = setInterval(function() {
|
||||
//console.log('****************** LOOP: Status Clock ******************');
|
||||
//https://date-fns.org/v1.30.1/docs/format
|
||||
let datetime_string = dateFns.format(new Date(), 'dddd, Do h:mm:ss A'); //dddd, Do hh:mm:ss A
|
||||
document.getElementById('app_datetime').innerHTML = datetime_string;
|
||||
}, 1000);
|
||||
|
||||
|
||||
// END: Status clock section
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<!-- <script src="js/test.js"></script>-->
|
||||
<!-- JavaScript (JS) end -->
|
||||
|
||||
</body>
|
||||
</html>
|
||||
204
app/index.html
204
app/index.html
@@ -1,204 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
||||
<title>One Sky IT's Aether App</title>
|
||||
|
||||
<link rel="shortcut icon" type="image/png" href="img/favicon.ico">
|
||||
|
||||
<!-- Cascading Style Sheets (CSS) start -->
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
|
||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,400;0,700;1,400;1,700&display=swap" rel="stylesheet">
|
||||
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap" rel="stylesheet">
|
||||
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" integrity="sha512-1ycn6IcaQQ40/MKBW2W4Rhis/DbILU74C1vSrLJxCq57o941Ym01SwNsOMqvEBFlcgUa6xLiPY/NS5R+E6ztJQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||
|
||||
<!-- One Sky IT default custom Cascading Style Sheets (CSS) -->
|
||||
|
||||
<!-- One Sky IT site custom Cascading Style Sheets (CSS) -->
|
||||
<link rel="stylesheet" href="http://dev.oneskyit.local:5000/static/css/aether_variables.css">
|
||||
<link rel="stylesheet" href="http://dev.oneskyit.local:5000/static/css/aether_utilities.css">
|
||||
<link rel="stylesheet" href="http://dev.oneskyit.local:5000/static/css/aether_system.css">
|
||||
<!-- <link rel="stylesheet" href="svelte/build/aether_layout.css"> -->
|
||||
<!-- <link rel="stylesheet" href="css/aether_layout_flow.css"> -->
|
||||
<!-- <link rel="stylesheet" href="css/aether_layout_grid.css"> -->
|
||||
<link rel="stylesheet" href="http://dev.oneskyit.local:5000/static/css/aether_modules_core.css">
|
||||
<link rel="stylesheet" href="http://dev.oneskyit.local:5000/static/css/aether_modules_other.css">
|
||||
|
||||
<link href="http://dev.oneskyit.local:5000/static/svelte/build/bundle.css" rel="stylesheet">
|
||||
<link href="http://dev.oneskyit.local:5000/static/svelte/build/reloading.css" rel="stylesheet">
|
||||
|
||||
<link href="http://dev.oneskyit.local:5000/static/css/base_style_grid_layout_v3.css" rel="stylesheet">
|
||||
<link href="http://dev.oneskyit.local:5000/static/css/base_style_grid_theme_v3.css" rel="stylesheet">
|
||||
|
||||
<link href="http://dev.oneskyit.local:5000/event/assets/css/event_launcher.css" rel="stylesheet">
|
||||
|
||||
<link rel="stylesheet" href="css/aether_native_app_v3.css">
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<!-- Cascading Style Sheets (CSS) end -->
|
||||
|
||||
<!-- JavaScript (JS) start -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.22.0/axios.min.js" integrity="sha512-m2ssMAtdCEYGWXQ8hXVG4Q39uKYtbfaJL5QMTbhl2kc6vYyubrKHhr6aLLXW4ITeXSywQLn1AhsAaqrJl8Acfg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
|
||||
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.2.0/socket.io.js" integrity="sha512-WL6WGKMPBiM9PnHRYIn5YEtq0Z8XP4fkVb4qy7PP4vhmYQErJ/dySyXuFIMDf1eEYCXCrQrMJfkNwKc9gsjTjA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.10.7/dayjs.min.js" integrity="sha512-bwD3VD/j6ypSSnyjuaURidZksoVx3L1RPvTkleC48SbHCZsemT3VKMD39KknPnH728LLXVMTisESIBOAb5/W0Q==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/localforage/1.10.0/localforage.min.js" integrity="sha512-+BMamP0e7wn39JGL8nKAZ3yAQT2dL5oaXWr4ZYlTGkKOaoXM/Yj7c4oy50Ngz5yoUutAG17flueD4F6QpTlPng==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
|
||||
<script src="https://static.oneskyit.com/js/utilities.js"></script>
|
||||
<!-- <script src="js/app_idb.js"></script> -->
|
||||
|
||||
<script>const app = require('./js/aether_native_app_v3');</script>
|
||||
<script>
|
||||
let app_config = app.load_config();
|
||||
|
||||
const flask_env = 'development'; // 'development', 'production'
|
||||
const env = 'development'; // 'development', 'production'
|
||||
const app_env = 'development_local'; // 'development_local', 'development_remote'
|
||||
const api_env = 'development_local'; // 'development_local', 'development_remote'
|
||||
const app_mode = 'app'; // null, 'default', 'onsite', 'app'
|
||||
|
||||
let client_account_id = app_config.account_id;
|
||||
console.log(client_account_id);
|
||||
let event_id = app_config.event_id;
|
||||
let event_device_id = app_config.event_device_id;
|
||||
let event_location_id = app_config.event_location_id;
|
||||
|
||||
const page_for = { 'event': event_id, 'event_device': event_device_id, 'event_location': event_location_id }; // Simple key value like object
|
||||
console.log(page_for);
|
||||
|
||||
const host_file_cache_path = 'file_cache'; // app_config.host_file_cache_path; // 'file_cache/'
|
||||
console.log(host_file_cache_path);
|
||||
const host_file_temp_path = 'temp' // app_config.host_file_temp_path; // 'temp/'
|
||||
console.log(host_file_temp_path);
|
||||
|
||||
let idb_name = app_config.idb_name;
|
||||
|
||||
</script>
|
||||
|
||||
<!-- <script defer src="svelte/build/bundle.js" crossorigin></script> -->
|
||||
<script defer src="http://dev.oneskyit.local:5000/static/svelte/build/bundle.js" crossorigin></script>
|
||||
<!-- JavaScript (JS) end -->
|
||||
|
||||
</head>
|
||||
|
||||
<body class="body_container">
|
||||
|
||||
|
||||
<section id="System-Nav-Menu">Site-Nav-Menu</section>
|
||||
<section id="Site-Header">Site-Header</section>
|
||||
<section id="Site-Nav-Menu">Site-Nav-Menu</section>
|
||||
|
||||
<section id="System-Notifications">System-Notifications (and Site-Notifications)</section><!-- and what would be Site-Notifications-->
|
||||
|
||||
<section id="Main-Body" class="main_template_content svelte_target event_launcher_main">
|
||||
</section>
|
||||
|
||||
<section id="Site-Footer">Site-Footer</section>
|
||||
<section id="System-Footer">Site-Footer</section>
|
||||
|
||||
<section id="System-Debug">System-Debug</section>
|
||||
|
||||
|
||||
</body>
|
||||
<!-- JavaScript (JS) start -->
|
||||
|
||||
<!-- One Sky IT default custom JavaScript (JS) -->
|
||||
|
||||
<!-- One Sky IT site custom JavaScript (JS) -->
|
||||
<!-- <script>const app = require('./js/app_v3');</script> -->
|
||||
<!-- <script src="js/app_v3.js"></script> -->
|
||||
<script>
|
||||
/* ***** **** *** ** * ### * ** *** **** ***** */
|
||||
|
||||
// BEGIN: API section
|
||||
|
||||
const access_control_allow_origin = app_config.access_control_allow_origin;
|
||||
const api_secret_key = app_config.api_secret_key;
|
||||
console.log(api_secret_key);
|
||||
let api_temporary_token = null;
|
||||
|
||||
let api_base_url = null;
|
||||
|
||||
if (app_config.use_local_api) {
|
||||
api_base_url = app_config.api_local_base_url; // 'http://api.localhost:5001'
|
||||
} else {
|
||||
api_base_url = app_config.api_remote_base_url; // 'https://api.oneskyit.com'
|
||||
}
|
||||
|
||||
/* ***** **** *** ** * ### * ** *** **** ***** */
|
||||
|
||||
let app_online = false;
|
||||
//let app_use_cached_data = true;
|
||||
window.addEventListener('online', app.currently_online);
|
||||
window.addEventListener('offline', app.currently_offline);
|
||||
|
||||
/* ***** **** *** ** * ### * ** *** **** ***** */
|
||||
|
||||
// BEGIN: Load IDB section
|
||||
// let idb_name = app_config.idb_name;
|
||||
|
||||
// let tbl_event = null;
|
||||
// let tbl_event_location = null;
|
||||
// let tbl_event_session = null;
|
||||
// let tbl_event_presentation = null;
|
||||
// let tbl_event_presenter = null;
|
||||
// let tbl_event_file = null;
|
||||
// let tbl_hosted_file = null;
|
||||
|
||||
// let tbl_event_key_count = null;
|
||||
// let tbl_event_location_key_count = null;
|
||||
// let tbl_event_session_key_count = null;
|
||||
// let tbl_event_presentation_key_count = null;
|
||||
// let tbl_event_presenter_key_count = null;
|
||||
// let tbl_event_file_key_count = null;
|
||||
// let tbl_hosted_file_key_count = null;
|
||||
|
||||
// let load_idb_tables_result = load_idb_tables()
|
||||
// .then(async function (result) {
|
||||
// console.log('IDB tables have been opened');
|
||||
// idb_tables_opened = load_idb_tables_result;
|
||||
|
||||
// console.log('Attempting to use cached IDB data...');
|
||||
|
||||
// if (tbl_event_key_count
|
||||
// && tbl_event_location_key_count
|
||||
// && tbl_event_session_key_count
|
||||
// && tbl_event_presentation_key_count
|
||||
// && tbl_event_presenter_key_count
|
||||
// && tbl_event_file_key_count) {
|
||||
// console.log('********* Not yet sure how this works... ');
|
||||
// } else {
|
||||
// console.log('One or more of the IDB tables has 0 records. The IDB probably needs to be updated.');
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// console.log(tbl_event);
|
||||
// console.log(tbl_event_location);
|
||||
// console.log(tbl_event_session);
|
||||
// console.log(tbl_event_presentation);
|
||||
// console.log(tbl_event_presenter);
|
||||
// console.log(tbl_event_file);
|
||||
// console.log(tbl_hosted_file);
|
||||
// })
|
||||
// .catch(function (error) {
|
||||
// console.log('Something went wrong opening the IDB tables.');
|
||||
// console.log(error);
|
||||
// return false;
|
||||
// });
|
||||
|
||||
/* ***** **** *** ** * ### * ** *** **** ***** */
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
</html>
|
||||
@@ -1,365 +0,0 @@
|
||||
'use strict';
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { ipcRenderer } = require('electron');
|
||||
|
||||
|
||||
exports.load_config = function () {
|
||||
console.log('*** Electron framework: load_config() ***');
|
||||
console.log('CWD: '+process.cwd());
|
||||
|
||||
let home_directory = require('os').homedir();
|
||||
console.log('Home: '+home_directory);
|
||||
|
||||
let tmp_directory = require('os').tmpdir();
|
||||
console.log('Temporary: '+tmp_directory);
|
||||
|
||||
let config = null;
|
||||
let config_directory = null;
|
||||
let default_config_path = path.join(process.cwd(),'config.json.default');
|
||||
let config_path = null;
|
||||
|
||||
if (os.platform == 'darwin') {
|
||||
config_directory = path.join(home_directory, 'Library/Application Support/OSIT');
|
||||
console.log('macOS config directory: '+config_directory);
|
||||
} else if (os.platform == 'linux') {
|
||||
config_directory = path.join(home_directory, '.config/OSIT');
|
||||
console.log('Linux config directory: '+config_directory);
|
||||
}
|
||||
|
||||
if (fs.existsSync(config_directory)) {
|
||||
console.log('Config: '+config_directory);
|
||||
config_path = path.join(config_directory, 'config.json');
|
||||
} else {
|
||||
fs.mkdirSync(config_directory);
|
||||
console.log('Config directory created: '+config_directory);
|
||||
|
||||
//default_config_path = path.join(process.cwd(),'config.json.default');
|
||||
config_path = path.join(config_directory, 'config.json');
|
||||
fs.copyFileSync(default_config_path, config_path);
|
||||
console.log('Default config file copied: '+config_directory);
|
||||
}
|
||||
|
||||
if (fs.existsSync(config_path)) {
|
||||
console.log('Config path: '+config_path);
|
||||
console.log('Config file (config.json) found under '+config_directory+'.');
|
||||
|
||||
config = JSON.parse(fs.readFileSync(config_path));
|
||||
console.log('Config file read.');
|
||||
} else if (!fs.existsSync(config_path)) {
|
||||
fs.copyFileSync(default_config_path, config_path);
|
||||
console.log('Default config file copied: '+config_directory);
|
||||
|
||||
config = JSON.parse(fs.readFileSync(config_path));
|
||||
console.log('Config file read.');
|
||||
} else if (fs.existsSync('config.json')) {
|
||||
//fs.copyFileSync(default_config_path, config_path);
|
||||
//console.log('Default config file copied: '+config_directory);
|
||||
|
||||
config = JSON.parse(fs.readFileSync('config.json'));
|
||||
console.log('Config file (config.json) not found under '+config_directory+'. Using config in CWD.');
|
||||
console.log('Config file read.');
|
||||
|
||||
//console.log('Config file (config.json) not found under '+config_directory+'. Using config in CWD.');
|
||||
//config = JSON.parse(fs.readFileSync('config.json'));
|
||||
} else {
|
||||
//close();
|
||||
}
|
||||
//console.log(config);
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
// Check for local file
|
||||
// Updated 2022-03-10
|
||||
exports.check_local_file = async function ({local_file_path, filename}) {
|
||||
console.log('*** Electron framework export: check_local_file() ***');
|
||||
// console.log('Check for local file');
|
||||
console.log(`Local File Path: ${local_file_path}; Filename: ${filename}`);
|
||||
|
||||
let full_local_file_path = path.join(local_file_path, filename);
|
||||
console.log(full_local_file_path);
|
||||
|
||||
if (fs.existsSync(full_local_file_path)) {
|
||||
console.log(`Local file exists: ${full_local_file_path}`);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Check local hash file cache
|
||||
// Updated 2022-03-09
|
||||
exports.check_hash_file_cache = async function ({host_file_cache_path, hash}) {
|
||||
console.log('*** Electron framework export: check_hash_file_cache() ***');
|
||||
// console.log('Check local hash file cache');
|
||||
console.log(`Host File Cache Path: ${host_file_cache_path}; Hash: ${hash}`);
|
||||
|
||||
let hash_filename = `${hash}.file`;
|
||||
|
||||
let hash_file_cache_path = path.join(host_file_cache_path, hash_filename);
|
||||
// console.log(hash_file_cache_path);
|
||||
|
||||
if (fs.existsSync(hash_file_cache_path)) {
|
||||
console.log(`Hashed file exists in cache: ${hash_file_cache_path}`);
|
||||
return true;
|
||||
} else {
|
||||
console.log(`Hashed file not found in cache: ${hash_file_cache_path}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Download hash file to cache
|
||||
// Updated 2022-03-09
|
||||
exports.download_hash_file_to_cache = async function ({host_file_cache_path, event_file_id=null, hash=null}) {
|
||||
console.log('*** Electron framework export: download_hash_file_to_cache() ***');
|
||||
// console.log('Download hash file to cache');
|
||||
console.log(`Host File Cache Path: ${host_file_cache_path}; Event File ID: ${event_file_id}; Hash: ${hash}`);
|
||||
|
||||
let endpoint = `/event/file/${event_file_id}/download`;
|
||||
|
||||
let hash_filename = `${hash}.file`;
|
||||
let hash_file_cache_path = path.join(host_file_cache_path, hash_filename);
|
||||
// console.log(hash_file_cache_path);
|
||||
|
||||
let download_file_result = await ipcRenderer.invoke('download_file', api_base_url, endpoint, hash_file_cache_path).then((result) => {
|
||||
console.log('IPC download file process finished');
|
||||
// console.log(result);
|
||||
return true;
|
||||
});
|
||||
|
||||
// console.log(download_file_result);
|
||||
// console.log('End: download_hash_file_to_cache()');
|
||||
if (download_file_result) {
|
||||
console.log('File downloaded successfully');
|
||||
return true;
|
||||
} else {
|
||||
console.log('File was not downloaded successfully');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Open cached hash file after copying to temp directory
|
||||
// Updated 2022-03-09
|
||||
exports.open_hash_file_to_temp = async function ({host_file_cache_path, hash, host_file_temp_path, filename}) {
|
||||
console.log('*** Electron framework export: open_hash_file_to_temp() ***');
|
||||
// console.log('Open cached hash file after copying to temp directory');
|
||||
console.log(`Host File Cache Path: ${host_file_cache_path}; Hash: ${hash}; Host File Temp Path: ${host_file_temp_path}; Filename: ${filename}`);
|
||||
|
||||
let open_hash_file_to_temp_result = await ipcRenderer.invoke('open_hash_file_to_temp', host_file_cache_path, hash, host_file_temp_path, filename).then((result) => {
|
||||
console.log('IPC open hash file to temp finished');
|
||||
console.log(result);
|
||||
return true;
|
||||
})
|
||||
|
||||
// let result = await ipcRenderer.send('open_local_file', host_file_cache_path, hash, host_file_temp_path, filename);
|
||||
// console.log(result);
|
||||
|
||||
console.log(open_hash_file_to_temp_result);
|
||||
console.log('End: open_hash_file_to_temp()');
|
||||
if (open_hash_file_to_temp_result) {
|
||||
console.log('File opened successfully');
|
||||
return true;
|
||||
} else {
|
||||
console.log('File was not opened successfully');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Open local file
|
||||
// Updated 2022-03-10
|
||||
exports.open_local_file = async function ({local_file_path, filename}) {
|
||||
console.log('*** Electron framework export: open_local_file() ***');
|
||||
// console.log('Open local file');
|
||||
console.log(`Local File Path: ${local_file_path}; Filename: ${filename}`);
|
||||
|
||||
// let full_local_file_path = path.join(local_file_path, filename);
|
||||
// console.log(full_local_file_path);
|
||||
|
||||
// if (fs.existsSync(full_local_file_path)) {
|
||||
// console.log(`Local file exists: ${full_local_file_path}`);
|
||||
// // return true;
|
||||
// } else {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
let open_local_file_result = await ipcRenderer.invoke('open_local_file', local_file_path, filename).then((result) => {
|
||||
console.log('IPC open local file finished');
|
||||
console.log(result);
|
||||
return true;
|
||||
})
|
||||
|
||||
console.log(open_local_file_result);
|
||||
console.log('End: open_local_file()');
|
||||
if (open_local_file_result) {
|
||||
console.log('File opened successfully');
|
||||
return true;
|
||||
} else {
|
||||
console.log('File was not opened successfully');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Check local file cache and download from server if needed.
|
||||
// Updated 2022-03-09
|
||||
// exports.check_file_cache = async function ({host_file_cache_path, event_file_id, hash}) {
|
||||
exports.check_file_cache = async function ({host_file_cache_path, event_file_id, hash}) {
|
||||
console.log('*** Electron framework export: check_file_cache() ***');
|
||||
// console.log('Check local file cache and download from server if needed.');
|
||||
console.log(`Host File Cache Path: ${host_file_cache_path}; Event File ID: ${event_file_id}; Hash: ${hash}`);
|
||||
|
||||
// NOTE: event_file_id is the event_file.id_random or event_file.event_file_id_random
|
||||
let hash_filename = hash+'.file';
|
||||
|
||||
let save_path = path.join(host_file_cache_path, hash_filename);
|
||||
console.log(save_path);
|
||||
|
||||
if (fs.existsSync(save_path)) {
|
||||
console.log('Hashed file cache already exists: '+save_path);
|
||||
return true;
|
||||
} else {
|
||||
console.log('Hashed file not found in local cache. Downloading file: '+save_path);
|
||||
let endpoint = `/event/file/${event_file_id}/download`;
|
||||
let result = await ipcRenderer.send('download_file', api_base_url, endpoint, save_path); // Must download file using main node.js thread.
|
||||
console.log(result);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
ipcRenderer.once('download_file_reply', function(event, response){
|
||||
console.log(response);
|
||||
return response;
|
||||
})
|
||||
resolve(true);
|
||||
});
|
||||
|
||||
// await ipcRenderer.once('download_file_reply', function(event, response){
|
||||
// console.log(response);
|
||||
// return response;
|
||||
// });
|
||||
|
||||
// result.then(function (response) {
|
||||
// console.log('Downloaded!!!???');
|
||||
// return true;
|
||||
// }).catch(function (error) {
|
||||
// console.log(error);
|
||||
// return false;
|
||||
// });
|
||||
|
||||
// return result;
|
||||
|
||||
// console.log(result);
|
||||
// if (result) {
|
||||
// return true;
|
||||
// } else {
|
||||
// return false;
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Check local file cache and download from server if needed. Must use IPC to Main to download file. Set a Promise to wait for download_file_reply.
|
||||
// Updated 2022-03-09
|
||||
async function check_file_cache({host_file_cache_path, event_file_id, hash}) {
|
||||
console.log('*** Electron framework: check_file_cache() ***');
|
||||
// console.log('Check local file cache and download from server if needed.');
|
||||
console.log(`Host File Cache Path: ${host_file_cache_path}; Event File ID: ${event_file_id}; Hash: ${hash}`);
|
||||
|
||||
// NOTE: event_file_id is the event_file.id_random or event_file.event_file_id_random
|
||||
let hash_filename = hash+'.file';
|
||||
|
||||
let save_path = path.join(host_file_cache_path, hash_filename);
|
||||
console.log(save_path);
|
||||
|
||||
if (fs.existsSync(save_path)) {
|
||||
console.log('Hashed file cache already exists: '+save_path);
|
||||
return true;
|
||||
} else {
|
||||
console.log('Hashed file not found in local cache. Downloading file: '+save_path);
|
||||
let endpoint = `/event/file/${event_file_id}/download`;
|
||||
let result = await ipcRenderer.send('download_file', api_base_url, endpoint, save_path); // Must download file using main node.js thread.
|
||||
console.log(result);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
ipcRenderer.once('download_file_reply', function(event, response){
|
||||
console.log(response);
|
||||
return response;
|
||||
})
|
||||
resolve(true);
|
||||
});
|
||||
|
||||
// await ipcRenderer.once('download_file_reply', function(event, response){
|
||||
// console.log(response);
|
||||
// return response;
|
||||
// });
|
||||
|
||||
// result.then(function (response) {
|
||||
// console.log('Downloaded!!!???');
|
||||
// return true;
|
||||
// }).catch(function (error) {
|
||||
// console.log(error);
|
||||
// return false;
|
||||
// });
|
||||
|
||||
// return result;
|
||||
|
||||
// console.log(result);
|
||||
// if (result) {
|
||||
// return true;
|
||||
// } else {
|
||||
// return false;
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
// IPC to Main: Open local file cache if available. Copy to temp directory with given filename first.
|
||||
// Updated 2022-03-09
|
||||
async function open_local_file({host_file_cache_path, hash, host_file_temp_path, filename}) {
|
||||
console.log('*** Electron framework: open_local_file() ***');
|
||||
// console.log('Open local file cache if available. Copy to temp directory with given filename first.');
|
||||
console.log(`Host File Cache Path: ${host_file_cache_path}; Hash: ${hash}; Host File Temp Path: ${host_file_temp_path}; Filename: ${filename}`);
|
||||
|
||||
console.log(host_file_cache_path);
|
||||
console.log(hash);
|
||||
console.log(filename);
|
||||
|
||||
let result = await ipcRenderer.send('open_local_file', host_file_cache_path, hash, host_file_temp_path, filename);
|
||||
console.log(result);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
exports.check_file_cache_and_open_local_file = async function ({host_file_cache_path, event_file_id, hash, host_file_temp_path, filename}) {
|
||||
console.log('*** Electron framework: check_file_cache_and_open_local_file() ***');
|
||||
console.log('Checking the local file cache against the remote server and then opening the local file.');
|
||||
|
||||
let check_file_cache_result = check_file_cache({host_file_cache_path: host_file_cache_path, event_file_id: event_file_id, hash: hash});
|
||||
console.log(check_file_cache_result);
|
||||
|
||||
if (check_file_cache_result) {
|
||||
let open_local_file_result = open_local_file({host_file_cache_path: host_file_cache_path, hash: hash, host_file_temp_path: host_file_temp_path, filename: filename});
|
||||
console.log(open_local_file_result);
|
||||
|
||||
return open_local_file_result;
|
||||
}
|
||||
|
||||
ipcRenderer.once('download_file_reply', function(event, response){
|
||||
console.log(response);
|
||||
|
||||
let open_local_file_result = open_local_file({host_file_cache_path: host_file_cache_path, hash: hash, host_file_temp_path: host_file_temp_path, filename: filename});
|
||||
console.log(open_local_file_result);
|
||||
|
||||
return open_local_file_result;
|
||||
})
|
||||
|
||||
|
||||
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 9.5 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 41 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 6.9 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 17 KiB |
@@ -1 +0,0 @@
|
||||
launcher reset
|
||||
@@ -1 +0,0 @@
|
||||
test txt
|
||||
@@ -1,878 +0,0 @@
|
||||
html {
|
||||
/* Parent Layout */
|
||||
/* Box Model */
|
||||
/* Positioning */
|
||||
/*height: 100%;*/
|
||||
/* Display */
|
||||
}
|
||||
|
||||
body {
|
||||
/* Parent Layout */
|
||||
/* Box Model */
|
||||
/* Positioning */
|
||||
margin-bottom: 2.25rem; /* full height */
|
||||
/* margin: 0; */
|
||||
/* padding: 0; */
|
||||
/* Display */
|
||||
}
|
||||
|
||||
nav {
|
||||
}
|
||||
|
||||
|
||||
/* *** BEGIN *** Layout *** System Menu (root menu) *** */
|
||||
|
||||
|
||||
/* *** END *** Layout *** System Menu (root menu) *** */
|
||||
|
||||
|
||||
|
||||
|
||||
/* Aether System Styles */
|
||||
/* Most of this should not be overridden by the client unless using something like an iframe for specific content. */
|
||||
|
||||
|
||||
/* *** BEGIN *** System *** General *** */
|
||||
/* *** END *** System *** General *** */
|
||||
|
||||
|
||||
/* *** BEGIN *** System *** System Menu (root menu) *** */
|
||||
.system_menu {
|
||||
z-index: 1040; /* Bootstrap's modal background is also z-index: 1040 */
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
|
||||
/*height: .1rem;*/
|
||||
height: 2.68rem;
|
||||
/*height: 6.12rem;*/
|
||||
width: 100vw;
|
||||
max-width: 1440px; /*100vw*/
|
||||
|
||||
transform: translateX(-50%);
|
||||
|
||||
margin: 0 0;
|
||||
margin-bottom: .1rem;
|
||||
|
||||
padding-top: .03rem;
|
||||
padding-left: .03rem;
|
||||
padding-right: .03rem;
|
||||
padding-bottom: .03rem;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
/*background: linear-gradient(to right, rgba(220,220,220,.9) 25%, rgba(128,128,128,.8) 50%, rgba(220,220,220,.9) 75%);*/
|
||||
|
||||
border: solid thick transparent;
|
||||
|
||||
border-top: none;
|
||||
border-left: solid thick hsl(var(--hue-green),var(--saturation-least),var(--lum-darkest));
|
||||
border-right: solid thick hsl(var(--hue-green),var(--saturation-least),var(--lum-darkest));
|
||||
border-bottom: solid thin hsl(var(--hue-green),var(--saturation-least),var(--lum-darkest));
|
||||
border-bottom: none;
|
||||
border-image:
|
||||
linear-gradient(
|
||||
to bottom,
|
||||
hsl(var(--hue-green),var(--saturation-least),var(--lum-darkest)),
|
||||
transparent
|
||||
) 1;
|
||||
|
||||
/* NOTE: transition when no longer hovering */
|
||||
transition-property: height;
|
||||
transition-delay: 4s;
|
||||
transition-duration: .1s;
|
||||
transition-timing-function: linear;
|
||||
}
|
||||
|
||||
.system_menu:hover {
|
||||
z-index: 1051; /* Bootstrap's modal background is z-index: 1040 */
|
||||
|
||||
border-left: solid thick hsl(var(--hue-green),var(--saturation-most),var(--lum-darkest));
|
||||
border-right: solid thick hsl(var(--hue-green),var(--saturation-most),var(--lum-darkest));
|
||||
border-bottom: solid thin hsl(var(--hue-green),var(--saturation-most),var(--lum-darkest));
|
||||
border-image:
|
||||
linear-gradient(
|
||||
to top,
|
||||
hsl(var(--hue-green),var(--saturation-most),var(--lum-darkest)),
|
||||
transparent
|
||||
) 1;
|
||||
|
||||
background-image: linear-gradient(to right, hsla(var(--hue-yellow), var(--saturation-less), var(--lum-lightest).1) 0%, hsla(var(--hue-yellow), var(--saturation-less), var(--lum-lightest),.9) 3%, hsla(var(--hue-yellow), var(--saturation-less), var(--lum-lightest),.9) 97%, hsla(var(--hue-yellow), var(--saturation-less), var(--lum-lightest),.1) 100%);
|
||||
|
||||
|
||||
height: 5.33rem;
|
||||
|
||||
/* NOTE: transition when hover starts */
|
||||
transition-property: height;
|
||||
transition-delay: 1.5s;
|
||||
transition-duration: .15s;
|
||||
transition-timing-function: linear;
|
||||
}
|
||||
|
||||
.system_menu_clicked {
|
||||
z-index: 1051; /* Bootstrap's modal background is z-index: 1040 */
|
||||
|
||||
border-left: solid thick hsl(var(--hue-green),var(--saturation-most),var(--lum-darkest));
|
||||
border-right: solid thick hsl(var(--hue-green),var(--saturation-most),var(--lum-darkest));
|
||||
border-bottom: solid thin hsl(var(--hue-green),var(--saturation-most),var(--lum-darkest));
|
||||
border-image:
|
||||
linear-gradient(
|
||||
to top,
|
||||
hsl(var(--hue-green),var(--saturation-most),var(--lum-darkest)),
|
||||
transparent
|
||||
) 1;
|
||||
|
||||
background-image: linear-gradient(to right, hsla(var(--hue-yellow), var(--saturation-less), var(--lum-lightest).1) 0%, hsla(var(--hue-yellow), var(--saturation-less), var(--lum-lightest),.9) 3%, hsla(var(--hue-yellow), var(--saturation-less), var(--lum-lightest),.9) 97%, hsla(var(--hue-yellow), var(--saturation-less), var(--lum-lightest),.1) 100%);
|
||||
|
||||
|
||||
height: 5.33rem;
|
||||
|
||||
/* NOTE: transition when hover starts */
|
||||
transition-property: height;
|
||||
transition-delay: 0s;
|
||||
transition-duration: .15s;
|
||||
transition-timing-function: linear;
|
||||
}
|
||||
|
||||
.system_menu_begin {
|
||||
/*border: solid thin red;*/
|
||||
align-self: flex-end;
|
||||
|
||||
display: flex;
|
||||
/*align-content: center;*/
|
||||
align-items: center;
|
||||
/*justify-content: center;*/
|
||||
|
||||
height: 100%;
|
||||
padding: 0 .25rem;
|
||||
|
||||
text-shadow: 0 0 .25rem hsla(var(--hue-blue), var(--saturation-least), var(--lum-lightest),1), 0 0 .75rem hsla(var(--hue-blue), var(--saturation-least), var(--lum-lightest),1), 0 0 1rem hsla(var(--hue-blue), var(--saturation-least), var(--lum-lightest),1);
|
||||
|
||||
color: black;
|
||||
|
||||
/*background-image: linear-gradient(to right, hsla(var(--hue-blue), var(--saturation-least), var(--lum-lighter),0) 0%, hsla(var(--hue-blue), var(--saturation-least), var(--lum-lighter),.9) 3%, hsla(var(--hue-blue), var(--saturation-least), var(--lum-lighter),.9) 97%, hsla(var(--hue-blue), var(--saturation-least), var(--lum-lighter),0) 100%);*/
|
||||
}
|
||||
.system_menu_begin:hover {
|
||||
}
|
||||
|
||||
.system_menu_center {
|
||||
/*border: solid thin blue;*/
|
||||
align-self: flex-end;
|
||||
flex: auto;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
/*background-image: linear-gradient(to right, hsla(var(--hue-blue), var(--saturation-least), var(--lum-mid),.1) 25%, hsla(var(--hue-blue), var(--saturation-least), var(--lum-mid),.8) 50%, hsla(var(--hue-blue), var(--saturation-least), var(--lum-mid),.9) 75%);*/
|
||||
}
|
||||
|
||||
.system_menu_end {
|
||||
/*border: solid thin red;*/
|
||||
align-self: flex-end;
|
||||
|
||||
display: flex;
|
||||
/*align-content: center;*/
|
||||
align-items: center;
|
||||
/*justify-content: center;*/
|
||||
|
||||
height: 100%;
|
||||
padding: 0 .25rem;
|
||||
|
||||
text-shadow: 0 0 .25rem white, 0 0 .75rem hsla(var(--hue-blue), var(--saturation-least), var(--lum-lightest),1), 0 0 1rem hsla(var(--hue-blue), var(--saturation-least), var(--lum-lightest),1);
|
||||
|
||||
color: black;
|
||||
|
||||
/*background-image: linear-gradient(to right, hsla(var(--hue-blue), var(--saturation-least), var(--lum-lightest),0) 0%, hsla(var(--hue-blue), var(--saturation-least), var(--lum-lightest),.6) 20%, hsla(var(--hue-blue), var(--saturation-least), var(--lum-lightest),.6) 80%, hsla(var(--hue-blue), var(--saturation-least), var(--lum-lightest),0) 100%);*/
|
||||
}
|
||||
.system_menu_end:hover {
|
||||
}
|
||||
|
||||
.system_menu_hidden {
|
||||
border-bottom: none;
|
||||
align-self: flex-end;
|
||||
flex: auto;
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
|
||||
margin: 0;
|
||||
padding: .1rem .75rem;
|
||||
|
||||
/*background-image: linear-gradient(to right, hsla(var(--hue-blue), var(--saturation-least), var(--lum-lightest),0) 0%, hsla(var(--hue-blue), var(--saturation-least), var(--lum-lightest),.9) 3%, hsla(var(--hue-blue), var(--saturation-least), var(--lum-lightest),.9) 97%, hsla(var(--hue-blue), var(--saturation-least), var(--lum-lightest),0) 100%);*/
|
||||
}
|
||||
.system_menu_hidden:hover {
|
||||
/*border-bottom: solid thin gray;*/
|
||||
}
|
||||
|
||||
.system_menu_visible {
|
||||
border-bottom: none;
|
||||
/*border-bottom: solid thin gray;*/
|
||||
align-self: flex-end;
|
||||
flex: auto;
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
|
||||
margin: 0;
|
||||
padding: .1rem .75rem;
|
||||
|
||||
/*background-image: linear-gradient(to right, hsla(var(--hue-blue), var(--saturation-least), var(--lum-lightest),0) 0%, hsla(var(--hue-blue), var(--saturation-least), var(--lum-lightest),.9) 3%, hsla(var(--hue-blue), var(--saturation-least), var(--lum-lightest),.9) 97%, hsla(var(--hue-blue), var(--saturation-least), var(--lum-lightest),0) 100%);*/
|
||||
}
|
||||
.system_menu_visible:hover {
|
||||
/*border-bottom: solid thin gray;*/
|
||||
}
|
||||
|
||||
.system_menu_name {
|
||||
/*border: dashed thin blue;*/
|
||||
/*flex: auto;*/
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-begin;
|
||||
align-items: center;
|
||||
|
||||
margin: 0;
|
||||
padding: 0rem .5rem;
|
||||
|
||||
background-image: linear-gradient(to right, hsla(var(--hue-blue), var(--saturation-least), var(--lum-lightest),0) 0%, hsla(var(--hue-blue), var(--saturation-least), var(--lum-lightest),.8) 3%, hsla(var(--hue-blue), var(--saturation-least), var(--lum-lightest),.8) 97%, hsla(var(--hue-blue), var(--saturation-least), var(--lum-lightest),0) 100%);
|
||||
}
|
||||
|
||||
.system_menu_name img {
|
||||
max-height: 1rem;
|
||||
}
|
||||
|
||||
.system_menu_information {
|
||||
/*border: dashed thin green;*/
|
||||
/*flex: auto;*/
|
||||
align-self: flex-end;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
|
||||
margin: 0;
|
||||
padding: 0rem .5rem;
|
||||
|
||||
background-image: linear-gradient(to right, hsla(var(--hue-blue), var(--saturation-least), var(--lum-lightest),0) 0%, hsla(var(--hue-blue), var(--saturation-least), var(--lum-lightest),.8) 3%, hsla(var(--hue-blue), var(--saturation-least), var(--lum-lightest),.8) 97%, hsla(var(--hue-blue), var(--saturation-least), var(--lum-lightest),0) 100%);
|
||||
}
|
||||
.system_menu_information:hover {
|
||||
/*background-color: gray;*/
|
||||
|
||||
/*background-image: linear-gradient(to right, hsla(var(--hue-blue), var(--saturation-least), var(--lum-lighter),0) 0%, hsla(var(--hue-blue), var(--saturation-least), var(--lum-lighter),.9) 3%, hsla(var(--hue-blue), var(--saturation-least), var(--lum-lighter),.9) 97%, hsla(var(--hue-blue), var(--saturation-least), var(--lum-lighter),0) 100%);*/
|
||||
|
||||
background-image: linear-gradient(to right, hsla(var(--hue-blue), var(--saturation-least), var(--lum-lightest),.0) 0%, hsla(var(--hue-blue), var(--saturation-least), var(--lum-lightest),.97) 3%, hsla(var(--hue-blue), var(--saturation-least), var(--lum-lightest),.97) 97%, hsla(var(--hue-blue), var(--saturation-least), var(--lum-lightest),.0) 100%);
|
||||
|
||||
}
|
||||
|
||||
|
||||
.system_menu_float {
|
||||
z-index: 1040;
|
||||
position: fixed;
|
||||
top: 2.75rem;
|
||||
right: .1rem;
|
||||
|
||||
background-color: rgba(220,175,175,.8);
|
||||
|
||||
padding: .1rem .5rem;
|
||||
|
||||
/*position: fixed; top: 0; right: 0; */
|
||||
}
|
||||
|
||||
/* Set style based on the user's permissions */
|
||||
.system_menu.user_super {
|
||||
border-left: solid thick hsl(var(--hue-red),var(--saturation-most),var(--lum-darkest));
|
||||
border-right: solid thick hsl(var(--hue-red),var(--saturation-most),var(--lum-darkest));
|
||||
border-bottom: solid thin hsl(var(--hue-red),var(--saturation-most),var(--lum-darkest));
|
||||
border-image:
|
||||
linear-gradient(
|
||||
to top,
|
||||
hsl(var(--hue-red),var(--saturation-most),var(--lum-darkest)),
|
||||
transparent
|
||||
) 1;
|
||||
|
||||
background-image:
|
||||
linear-gradient(
|
||||
to bottom,
|
||||
hsl(var(--hue-red),var(--saturation-most),var(--lum-darkest)),
|
||||
transparent
|
||||
);
|
||||
}
|
||||
|
||||
.system_menu.user_manager {
|
||||
border-left: solid thick hsl(var(--hue-red),var(--saturation-most),var(--lum-darkest));
|
||||
border-right: solid thick hsl(var(--hue-red),var(--saturation-most),var(--lum-darkest));
|
||||
border-bottom: solid thin hsl(var(--hue-red),var(--saturation-most),var(--lum-darkest));
|
||||
border-image:
|
||||
linear-gradient(
|
||||
to top,
|
||||
hsl(var(--hue-red),var(--saturation-most),var(--lum-darkest)),
|
||||
transparent
|
||||
) 1;
|
||||
|
||||
background-image:
|
||||
linear-gradient(
|
||||
to bottom,
|
||||
hsl(var(--hue-orange),var(--saturation-most),var(--lum-darkest)),
|
||||
transparent
|
||||
);
|
||||
}
|
||||
|
||||
.system_menu.user_administrator {
|
||||
border-left: solid thick hsl(var(--hue-red),var(--saturation-most),var(--lum-darkest));
|
||||
border-right: solid thick hsl(var(--hue-red),var(--saturation-most),var(--lum-darkest));
|
||||
border-bottom: solid thin hsl(var(--hue-red),var(--saturation-most),var(--lum-darkest));
|
||||
border-image:
|
||||
linear-gradient(
|
||||
to top,
|
||||
hsl(var(--hue-red),var(--saturation-most),var(--lum-darkest)),
|
||||
transparent
|
||||
) 1;
|
||||
|
||||
background-image:
|
||||
linear-gradient(
|
||||
to bottom,
|
||||
hsl(var(--hue-yellow),var(--saturation-most),var(--lum-darkest)),
|
||||
transparent
|
||||
);
|
||||
}
|
||||
|
||||
.system_menu.user_verified {
|
||||
border-left: solid thick hsl(var(--hue-red),var(--saturation-most),var(--lum-darkest));
|
||||
border-right: solid thick hsl(var(--hue-red),var(--saturation-most),var(--lum-darkest));
|
||||
border-bottom: solid thin hsl(var(--hue-red),var(--saturation-most),var(--lum-darkest));
|
||||
border-image:
|
||||
linear-gradient(
|
||||
to top,
|
||||
hsl(var(--hue-green),var(--saturation-most),var(--lum-darkest)),
|
||||
transparent
|
||||
) 1;
|
||||
|
||||
background-image:
|
||||
linear-gradient(
|
||||
to bottom,
|
||||
hsl(var(--hue-yellow),var(--saturation-most),var(--lum-darkest)),
|
||||
transparent
|
||||
);
|
||||
}
|
||||
|
||||
.system_menu.user_authenticated {
|
||||
border-left: solid thick hsl(var(--hue-red),var(--saturation-most),var(--lum-darkest));
|
||||
border-right: solid thick hsl(var(--hue-red),var(--saturation-most),var(--lum-darkest));
|
||||
border-bottom: solid thin hsl(var(--hue-red),var(--saturation-most),var(--lum-darkest));
|
||||
border-image:
|
||||
linear-gradient(
|
||||
to top,
|
||||
hsl(var(--hue-green),var(--saturation-most),var(--lum-darkest)),
|
||||
transparent
|
||||
) 1;
|
||||
|
||||
background-image:
|
||||
linear-gradient(
|
||||
to bottom,
|
||||
hsl(var(--hue-cyan),var(--saturation-most),var(--lum-darkest)),
|
||||
transparent
|
||||
);
|
||||
}
|
||||
|
||||
.system_menu .user_status_options {
|
||||
font-size: .8rem;
|
||||
line-height: 1rem;
|
||||
}
|
||||
/* *** END *** System *** System Menu (root menu) *** */
|
||||
|
||||
|
||||
/* *** BEGIN *** System *** User Login *** */
|
||||
.user_login .account__name--container {
|
||||
font-size: 1.1rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.user_login .account__none--container {
|
||||
font-size: 1.1rem;
|
||||
font-weight: bold;
|
||||
color: darkred;
|
||||
}
|
||||
|
||||
.user_login .show_password, .user_change_password .show_password {
|
||||
font-size: 1.1rem;
|
||||
line-height: 3.0rem;
|
||||
color: darkred;
|
||||
cursor: zoom-in; /*progress*/
|
||||
}
|
||||
|
||||
.user_login .show_password:hover, .user_change_password .show_password:hover {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
/* *** END *** System *** User Login *** */
|
||||
|
||||
|
||||
/* *** BEGIN *** System *** System Footer *** */
|
||||
/* system_footer or system_footer or system_status is the global (root) footer or status bar */
|
||||
.system_footer {
|
||||
position: fixed;
|
||||
/*width: 100vw;*/
|
||||
bottom: 0;
|
||||
|
||||
margin: 0 0;
|
||||
padding: 0 .5rem;
|
||||
|
||||
color: gray;
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
.system_footer .footer_left {
|
||||
float: left;
|
||||
}
|
||||
.system_footer .footer_right {
|
||||
float: right;
|
||||
}
|
||||
/* *** END *** System *** System Footer *** */
|
||||
|
||||
|
||||
/* *** BEGIN *** System *** Debug *** */
|
||||
.view_debug {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.hidden_debug {
|
||||
display: none;
|
||||
|
||||
position: fixed;
|
||||
bottom: 1.5rem;
|
||||
right: 0;
|
||||
background: pink;
|
||||
border: dashed thin pink;
|
||||
margin: .25rem .25rem .25rem;
|
||||
padding: .25rem;
|
||||
opacity: .5;
|
||||
|
||||
font-size: .7rem;
|
||||
|
||||
overflow-y: scroll;
|
||||
|
||||
max-height: 96.75vh;
|
||||
|
||||
z-index: 1050; /* Bootstrap 4 sticky-top class has a z-index of 1020 */
|
||||
}
|
||||
|
||||
.hidden_debug:hover {
|
||||
z-index: 1051;
|
||||
border: solid thin pink;
|
||||
opacity: .95;
|
||||
}
|
||||
/* *** END *** System *** Debug *** */
|
||||
|
||||
|
||||
/* BEGIN: Global System Classes */
|
||||
|
||||
|
||||
/* BEGIN: Main section of the page layout */
|
||||
|
||||
.site_header {
|
||||
padding-top: .5rem;
|
||||
padding-bottom: .5rem;
|
||||
margin-bottom: .25rem;
|
||||
|
||||
max-height: 15rem; /* Just in case something is added that is very high. */
|
||||
|
||||
width: 95%;
|
||||
max-width: 1400px;
|
||||
}
|
||||
|
||||
.site_header img {
|
||||
max-width: 100%;
|
||||
max-height: 8rem; /* Just in case something is added that is very high. */
|
||||
/*border: solid thin #aaa;*/
|
||||
border-radius: .25rem;
|
||||
}
|
||||
|
||||
/* primary_menu is generally the site_menu */
|
||||
.primary_menu {
|
||||
z-index: 1040; /* Bootstrap's modal background is also z-index: 1040 */
|
||||
/*padding-top: 2rem;*/
|
||||
|
||||
width: 95%;
|
||||
max-width: 1400px; /* 2048px or 2560px is 2K */
|
||||
margin-bottom: .25rem;
|
||||
|
||||
padding-right: 1.75rem;
|
||||
padding-left: 1.75rem;
|
||||
|
||||
background-image: linear-gradient(to right, rgba(255,255,255,.7) 0%, rgba(255,255,255,.95) 3%, rgba(255,255,255,.95) 97%, rgba(255,255,255,.7) 100%);
|
||||
}
|
||||
.primary_menu:hover {
|
||||
z-index: 1041; /* Bootstrap's modal background is also z-index: 1040 */
|
||||
}
|
||||
|
||||
|
||||
.flash_messages {
|
||||
width: 95%;
|
||||
max-width: 1400px; /* 2048px or 2560px is 2K */
|
||||
margin-bottom: .25rem;
|
||||
|
||||
padding-top: .25rem;
|
||||
padding-right: 1.75rem;
|
||||
padding-bottom: .25rem;
|
||||
padding-left: 1.75rem;
|
||||
|
||||
background-image: linear-gradient(to right, #ffff80 0%, #ffffe1 2%, #ffffe1 98%, #ffff80 100%);
|
||||
/*border: solid thin #ffff80;*/
|
||||
}
|
||||
|
||||
.flash_messages ul {
|
||||
margin: .25rem .25rem .25rem .25rem;
|
||||
padding: .25rem .25rem .25rem .25rem;
|
||||
}
|
||||
|
||||
.flash_messages li {
|
||||
list-style-type: circle;
|
||||
}
|
||||
|
||||
.flash_messages li.info {
|
||||
/*background-color: green;*/
|
||||
}
|
||||
|
||||
.flash_messages .flashes {
|
||||
}
|
||||
|
||||
.flash_messages.show_dev .debug {
|
||||
}
|
||||
|
||||
/* BEGIN: main_template_content section of the page layout */
|
||||
|
||||
.main_template_container {
|
||||
/*border: dashed medium darkred;
|
||||
* background-color: red;*/
|
||||
|
||||
position: relative;
|
||||
/*display: flex;*/
|
||||
align-items: stretch;
|
||||
|
||||
height: auto;
|
||||
min-height: 60vh;
|
||||
width: 95%;
|
||||
max-width: 1400px; /* 2048px or 2560px is 2K */
|
||||
|
||||
margin: .5rem auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
/* secondary_menu is generally the page_menu or object_type_menu */
|
||||
.secondary_menu {
|
||||
/*border: dashed medium darkgreen;
|
||||
* background-color: green;*/
|
||||
|
||||
/*position: relative;*/
|
||||
|
||||
margin-top: .25rem;
|
||||
margin-right: 0rem;
|
||||
margin-bottom: .25rem;
|
||||
margin-left: 0rem;
|
||||
|
||||
padding-top: .25rem;
|
||||
padding-right: 1.75rem;
|
||||
padding-bottom: .25rem;
|
||||
padding-left: 1.75rem;
|
||||
|
||||
width: 100%;
|
||||
min-width: 100%;
|
||||
max-width: 100%;
|
||||
|
||||
background-image: linear-gradient(to right, rgba(255,255,255,.7) 0%, rgba(255,255,255,.95) 3%, rgba(255,255,255,.95) 97%, rgba(255,255,255,.7) 100%);
|
||||
}
|
||||
|
||||
.secondary_menu>.btn-toolbar>.btn-group {
|
||||
margin-left: .1em;
|
||||
margin-right: .1em;
|
||||
}
|
||||
|
||||
.secondary_menu>.btn-toolbar>.btn-group>.btn {
|
||||
margin-left: .05em;
|
||||
margin-right: .05em;
|
||||
/*
|
||||
* border-left: solid thin #aaa;
|
||||
* border-right: solid thin #aaa;*/
|
||||
}
|
||||
|
||||
|
||||
.main_template_content {
|
||||
/*border: dashed medium darkgreen;
|
||||
* background-color: green;*/
|
||||
|
||||
position: relative;
|
||||
/*display: flex;
|
||||
* align-items: stretch;*/
|
||||
|
||||
margin-top: .25rem;
|
||||
margin-right: 0rem; /* Keep 0 to avoid horizontal scroll */
|
||||
margin-bottom: .25rem;
|
||||
margin-left: 0rem; /* Keep 0 to avoid horizontal scroll */
|
||||
padding-top: .5rem;
|
||||
padding-right: 1.75rem;
|
||||
padding-bottom: .5rem;
|
||||
padding-left: 1.75rem;
|
||||
|
||||
height: 100%;
|
||||
min-height: 50vh;
|
||||
max-height: 100%;
|
||||
width: 100%;
|
||||
min-width: 100%;
|
||||
max-width: 100%;
|
||||
|
||||
/*background: white;*/
|
||||
/*background-image: linear-gradient(to right, #eee 0%, #fff 2%, #fff 98%, #eee 100%);*/
|
||||
background-image: linear-gradient(to right, rgba(255,255,255,.7) 0%, rgba(255,255,255,.95) 3%, rgba(255,255,255,.95) 97%, rgba(255,255,255,.7) 100%);
|
||||
}
|
||||
|
||||
/* END: main_template_content section of the page layout */
|
||||
|
||||
|
||||
/* END: Global System Classes */
|
||||
|
||||
|
||||
/*X-Small devices (portrait phones, less than 576px)*/
|
||||
/*No media query for `xs` since this is the default in Bootstrap*/
|
||||
|
||||
/*x-Small devices (portrait phones, less than 576px)*/
|
||||
@media (max-width: 575px) {
|
||||
.viewport_width {
|
||||
padding-left: .5rem;
|
||||
padding-right: .5rem;
|
||||
background-color: blue;
|
||||
color: white;
|
||||
}
|
||||
.viewport_width::after {
|
||||
content: 'xs';
|
||||
padding-left: .5rem;
|
||||
}
|
||||
|
||||
header.site_header, nav.primary_menu.site_menu, section.flash_messages, div.main_template_container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.main_template_container {
|
||||
width: 100%;
|
||||
margin: 0rem auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.main_template_content {
|
||||
margin-top: .05rem;
|
||||
margin-right: 0rem; /* Keep 0 to avoid horizontal scroll */
|
||||
margin-bottom: .05rem;
|
||||
margin-left: 0rem; /* Keep 0 to avoid horizontal scroll */
|
||||
padding-top: .0rem;
|
||||
padding-right: .05rem;
|
||||
padding-bottom: .0rem;
|
||||
padding-left: .05rem;
|
||||
}
|
||||
|
||||
footer.system_footer {
|
||||
font-size: .8em;
|
||||
}
|
||||
}
|
||||
|
||||
/*Small devices (landscape phones, 576px and up)*/
|
||||
@media (min-width: 576px) {
|
||||
.viewport_width {
|
||||
padding-left: .5rem;
|
||||
padding-right: .5rem;
|
||||
background-color: green;
|
||||
color: black;
|
||||
}
|
||||
.viewport_width::after {
|
||||
content: 'sm';
|
||||
padding-left: .5rem;
|
||||
}
|
||||
|
||||
header.site_header, nav.primary_menu.site_menu, section.flash_messages, div.main_template_container {
|
||||
width: 97%;
|
||||
}
|
||||
|
||||
.main_template_container {
|
||||
width: 97%;
|
||||
margin: .25rem auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.main_template_content {
|
||||
margin-top: .1rem;
|
||||
margin-right: 0rem;
|
||||
margin-bottom: .1rem;
|
||||
margin-left: 0rem;
|
||||
padding-top: .25rem;
|
||||
padding-right: 1.00rem;
|
||||
padding-bottom: .25rem;
|
||||
padding-left: 1.00rem;
|
||||
}
|
||||
|
||||
|
||||
footer.system_footer {
|
||||
font-size: .85em;
|
||||
}
|
||||
}
|
||||
|
||||
/*Medium devices (tablets, 768px and up)*/
|
||||
@media (min-width: 768px) {
|
||||
.viewport_width {
|
||||
padding-left: .5rem;
|
||||
padding-right: .5rem;
|
||||
background-color: yellow;
|
||||
color: black;
|
||||
}
|
||||
.viewport_width::after {
|
||||
content: 'md';
|
||||
padding-left: .5rem;
|
||||
}
|
||||
|
||||
footer.system_footer {
|
||||
font-size: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
/*Large devices (desktops, 992px and up)*/
|
||||
@media (min-width: 992px) {
|
||||
.viewport_width {
|
||||
padding-left: .5rem;
|
||||
padding-right: .5rem;
|
||||
background-color: orange;
|
||||
color: black;
|
||||
}
|
||||
.viewport_width::after {
|
||||
content: 'lg';
|
||||
padding-left: .5rem;
|
||||
}
|
||||
}
|
||||
|
||||
/*X-Large devices (large desktops, 1200px and up)*/
|
||||
@media (min-width: 1200px) {
|
||||
.viewport_width {
|
||||
padding-left: .5rem;
|
||||
padding-right: .5rem;
|
||||
background-color: red;
|
||||
color: white;
|
||||
}
|
||||
.viewport_width::after {
|
||||
content: 'xl';
|
||||
padding-left: .5rem;
|
||||
}
|
||||
}
|
||||
|
||||
/*XX-Large devices (larger desktops, 1400px and up)*/
|
||||
@media (min-width: 1400px) {
|
||||
.viewport_width {
|
||||
background-color: black;
|
||||
color: white;
|
||||
}
|
||||
.viewport_width::after {
|
||||
content: 'xxl';
|
||||
padding-left: .5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media print {
|
||||
@page {
|
||||
size: Letter; /*Legal*/
|
||||
margin: .5in !important; /* top and bottom margin is set to .41 because of Google Chrome */
|
||||
padding: 0in;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: white !important;
|
||||
background-image: none !important;
|
||||
color: black !important;
|
||||
|
||||
margin: .25in !important;
|
||||
padding: .1in !important;
|
||||
|
||||
border: solid thin #eeeeee;
|
||||
}
|
||||
|
||||
.system_menu {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.system_menu_float {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.site_header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.site_menu {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.flash_messages {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.main_template_container {
|
||||
/*border: dashed thin red;*/
|
||||
|
||||
background-color: white !important;
|
||||
background-image: none !important;
|
||||
color: black !important;
|
||||
|
||||
margin: .01in !important;
|
||||
padding: .0in !important;
|
||||
|
||||
min-width: 0 !important;
|
||||
/*width: 0 !important;*/
|
||||
max-width: 100% !important;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.secondary_menu {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.main_template_content {
|
||||
/*border: dotted thin red;*/
|
||||
|
||||
background-color: white !important;
|
||||
background-image: none !important;
|
||||
color: black !important;
|
||||
|
||||
margin: .01in !important;
|
||||
padding: .01in !important;
|
||||
|
||||
min-width: 0 !important;
|
||||
/*width: 0 !important;*/
|
||||
max-width: 100% !important;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.system_footer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hidden_debug {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.page_help {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.page_links {
|
||||
float: right;
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,60 +0,0 @@
|
||||
/* This file will trigger the browser to reload quickly */
|
||||
|
||||
/* Component types */
|
||||
/* A section is a main component and should be outlined. It may be displayed as block. */
|
||||
section.component {
|
||||
outline: dashed thin;
|
||||
outline-color: var(--default-color-lighter);
|
||||
}
|
||||
section.component:hover {
|
||||
outline: dashed thin;
|
||||
outline-color: var(--default-color-light);
|
||||
}
|
||||
/* A div is a shared component, block, and should not be styled internally */
|
||||
div.component {
|
||||
outline: dotted thin;
|
||||
outline-color: var(--default-color-lighter);
|
||||
}
|
||||
div.component:hover {
|
||||
outline-color: var(--default-color-light);
|
||||
}
|
||||
/* A span is a shared component, inline, and should not be styled internally */
|
||||
span.component {
|
||||
outline: dotted thin;
|
||||
outline-color: var(--default-color-lighter);
|
||||
}
|
||||
span.component:hover {
|
||||
outline-color: var(--default-color-light);
|
||||
}
|
||||
|
||||
|
||||
.main_template_container {
|
||||
/*outline: dashed medium green;*/
|
||||
}
|
||||
|
||||
|
||||
.main_template_content {
|
||||
/*outline: dashed thin red;*/
|
||||
|
||||
}
|
||||
|
||||
.qr_scanner_form {
|
||||
/*outline: solid thin green;*/
|
||||
}
|
||||
|
||||
.qr_scanner {
|
||||
/*outline: solid thin pink;*/
|
||||
}
|
||||
|
||||
|
||||
.badge_body {
|
||||
/*outline: dotted thin red;*/
|
||||
}
|
||||
|
||||
.badge_person_name {
|
||||
/*outline: dashed thin pink;*/
|
||||
}
|
||||
|
||||
.badge_affiliations_location {
|
||||
/*outline: dashed thin pink;*/
|
||||
}
|
||||
69
config.json
69
config.json
@@ -1,69 +0,0 @@
|
||||
{
|
||||
"account_id": "_XY7DXtc9MY",
|
||||
"event_id": "pjrcghqwert",
|
||||
"event_device_id": "dbgMWS3KEHE",
|
||||
"event_location_id": null,
|
||||
"event_session_id": null,
|
||||
"event_presentation_id": null,
|
||||
"event_presenter_id": null,
|
||||
"event_file_id": null,
|
||||
"api_auth_loop_interval": 100,
|
||||
"api_token_update_loop_interval": 120000,
|
||||
"open_tables_loop_interval": 10,
|
||||
"update_idb_loop_interval": 10,
|
||||
"idb_to_launcher_loop_interval": 250,
|
||||
"update_render_loop_interval": 1000,
|
||||
"check_file_cache_loop_interval": 250,
|
||||
"api_secret_key": "dFP6J9DVj9hUgIMn-fNIqg",
|
||||
"use_local_api": true,
|
||||
"use_local_db": false,
|
||||
"api_remote_base_url": "https://dev-fastapi.oneskyit.com",
|
||||
"api_local_base_url": "http://dev-fastapi.oneskyit.local:5005",
|
||||
"access_control_allow_origin": "*",
|
||||
"remote_db_server": "db.oneskyit.com",
|
||||
"remote_db_port": "3306",
|
||||
"remote_db_name": "onesky_ams_test",
|
||||
"remote_db_username": "username_here",
|
||||
"remote_db_password": "password_here",
|
||||
"local_db_server": "db.oneskyit.local",
|
||||
"local_db_port": "3306",
|
||||
"local_db_name": "onesky_ams_test",
|
||||
"local_db_username": "username_here",
|
||||
"local_db_password": "password_here",
|
||||
"idb_name": "osit",
|
||||
"idb_event_check_period": 120000,
|
||||
"idb_event_location_check_period": 90000,
|
||||
"idb_event_session_check_period": 90000,
|
||||
"idb_event_presentation_check_period": 90000,
|
||||
"idb_event_presenter_check_period": 60000,
|
||||
"idb_event_file_check_period": 30000,
|
||||
"host_file_cache_path": "file_cache",
|
||||
"host_file_cache_check_period": 30000,
|
||||
"host_file_temp_path": "temp",
|
||||
"display_arrangement": "mirror_and_extend",
|
||||
"display_builtin_resolution": "",
|
||||
"display_builtin_refresh": "",
|
||||
"display_builtin_rotation": "",
|
||||
"display_external_resolution": "",
|
||||
"display_external_refresh": "",
|
||||
"display_external_rotation": "",
|
||||
"audio_out_volume": null,
|
||||
"audio_in_volume": null,
|
||||
"recording_fps": 30,
|
||||
"recording_show_cursor": true,
|
||||
"recording_highlight_clicks": false,
|
||||
"recording_screen_id": null,
|
||||
"recording_audio_device_id": null,
|
||||
"known_builtin_screen_ids": [69732032, 69733952, 69733248],
|
||||
"known_builtin_audio_device_ids": [ "AppleHDAEngineInput:1B,0,1,0:1", "BuiltInMicrophoneDevice" ],
|
||||
"recording_video_codec": "h264",
|
||||
"recording_path": "[home]/recordings",
|
||||
"recording_base_filename": "recording",
|
||||
"aperture_bin_path": null,
|
||||
"recording_start_datetime": "2019-10-12 01:01:01Z",
|
||||
"recording_stop_datetime": "2019-10-31 23:59:59Z",
|
||||
"recordings_datetime": [
|
||||
{ "start": "2019-10-11T09:50:00.00", "stop": "2019-10-11T10:15:00.00" },
|
||||
{ "start": "2019-10-11T10:50:00.00", "stop": "2019-10-11T11:15:00.00" }
|
||||
]
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
{
|
||||
"account_id": "_XY7DXtc9MY",
|
||||
"event_id": "pjrcghqwert",
|
||||
"event_location_id": "LUNt2zdMKCUNb9VReMZu8A",
|
||||
"main_loop_interval": 2000,
|
||||
"api_token_loop_interval": 1000,
|
||||
"api_update_period": 90000,
|
||||
"api_secret_key": "dFP6J9DVj9hUgIMn-fNIqg",
|
||||
"use_local_api": true,
|
||||
"use_local_db": false,
|
||||
"api_remote_base_url": "https://dev-fastapi.oneskyit.com",
|
||||
"api_local_base_url": "http://dev-fastapi.oneskyit.local:5005",
|
||||
"access_control_allow_origin": "*",
|
||||
"remote_db_server": "linode.oneskyit.com",
|
||||
"remote_db_port": "3306",
|
||||
"remote_db_name": "onesky_ams_test",
|
||||
"remote_db_username": "username_here",
|
||||
"remote_db_password": "password_here",
|
||||
"local_db_server": "db.oneskyit.local",
|
||||
"local_db_port": "3306",
|
||||
"local_db_name": "onesky_ams_test",
|
||||
"local_db_username": "username_here",
|
||||
"local_db_password": "password_here",
|
||||
"update_idb_loop_interval": 1000,
|
||||
"idb_name": "osit",
|
||||
"idb_check_period": 30000,
|
||||
"idb_event_check_period": 120000,
|
||||
"idb_event_location_check_period": 90000,
|
||||
"idb_event_session_check_period": 60000,
|
||||
"idb_event_presentation_check_period": 60000,
|
||||
"idb_event_presenter_check_period": 60000,
|
||||
"idb_event_file_check_period": 60000,
|
||||
"check_file_cache_loop_interval": 250,
|
||||
"host_file_cache_path": "file_cache",
|
||||
"host_file_cache_check_period": 30000,
|
||||
"update_render_loop_interval": 1000,
|
||||
"display_menu_session_times": true,
|
||||
"display_session_codes": true,
|
||||
"display_session_badges": true,
|
||||
"display_presentation_codes": true,
|
||||
"display_presentation_badges": true,
|
||||
"display_presenter_codes": true,
|
||||
"display_presenter_badges": true,
|
||||
"display_arrangement": "mirror_and_extend",
|
||||
"display_builtin_resolution": "",
|
||||
"display_builtin_refresh": "",
|
||||
"display_builtin_rotation": "",
|
||||
"display_external_resolution": "",
|
||||
"display_external_refresh": "",
|
||||
"display_external_rotation": "",
|
||||
"audio_out_volume": null,
|
||||
"audio_in_volume": null,
|
||||
"fps": 30,
|
||||
"show_cursor": true,
|
||||
"highlight_clicks": false,
|
||||
"screen_id": null,
|
||||
"audio_device_id": null,
|
||||
"known_builtin_screen_ids": [69732032, 69733952, 69733248],
|
||||
"known_builtin_audio_device_ids": [ "AppleHDAEngineInput:1B,0,1,0:1", "BuiltInMicrophoneDevice" ],
|
||||
"video_codec": "h264",
|
||||
"recordings_path": "[home]/recordings",
|
||||
"base_filename": "recording",
|
||||
"aperture_bin_path": null,
|
||||
"recording_start_datetime": "2019-10-12 01:01:01Z",
|
||||
"recording_stop_datetime": "2019-10-31 23:59:59Z",
|
||||
"recordings_datetime": [
|
||||
{ "start": "2019-10-11T09:50:00.00", "stop": "2019-10-11T10:15:00.00" },
|
||||
{ "start": "2019-10-11T10:50:00.00", "stop": "2019-10-11T11:15:00.00" }
|
||||
]
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"api_secret_key": "YWAAk39H2qH0edK6lPH0yg",
|
||||
"api_remote_base_url": "https://api.oneskyit.com",
|
||||
"api_local_base_url": "http://api.localhost:5001",
|
||||
"remote_db_server": "db.oneskyit.com",
|
||||
"remote_db_port": "3306",
|
||||
"remote_db_name": "onesky_ams_test",
|
||||
"remote_db_username": "username_here",
|
||||
"remote_db_password": "password_here",
|
||||
"local_db_server": "db.localhost",
|
||||
"local_db_port": "3306",
|
||||
"local_db_name": "onesky_ams_test",
|
||||
"local_db_username": "username_here",
|
||||
"local_db_password": "password_here",
|
||||
"local_file_cache":"file_cache",
|
||||
"display_arrangement": "mirror_and_extend",
|
||||
"display_builtin_resolution": "",
|
||||
"display_builtin_refresh": "",
|
||||
"display_builtin_rotation": "",
|
||||
"display_external_resolution": "",
|
||||
"display_external_refresh": "",
|
||||
"display_external_rotation": "",
|
||||
"audio_out_volume": "",
|
||||
"audio_in_volume": "",
|
||||
"fps": 30,
|
||||
"show_cursor": true,
|
||||
"highlight_clicks": false,
|
||||
"screen_id": null,
|
||||
"audio_device_id": null,
|
||||
"known_builtin_screen_ids": [69732032, 69733952, 69733248],
|
||||
"known_builtin_audio_device_ids": [ "AppleHDAEngineInput:1B,0,1,0:1", "BuiltInMicrophoneDevice" ],
|
||||
"video_codec": "h264",
|
||||
"recordings_path": "[home]/recordings",
|
||||
"base_filename": "recording",
|
||||
"aperture_bin_path": null,
|
||||
"recording_start_datetime": "2019-10-12 01:01:01Z",
|
||||
"recording_stop_datetime": "2019-10-31 23:59:59Z",
|
||||
"recordings_datetime": [
|
||||
{ "start": "2019-10-11T09:50:00.00", "stop": "2019-10-11T10:15:00.00" },
|
||||
{ "start": "2019-10-11T10:50:00.00", "stop": "2019-10-11T11:15:00.00" }
|
||||
]
|
||||
}
|
||||
69
dist/main/api_client.js
vendored
Normal file
69
dist/main/api_client.js
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.fetchFullConfig = fetchFullConfig;
|
||||
async function fetchFullConfig(seed) {
|
||||
const apiUrls = [
|
||||
seed.onsite_api_base_url,
|
||||
seed.primary_api_base_url,
|
||||
seed.backup_api_base_url
|
||||
].filter(url => url !== null && url !== undefined);
|
||||
let lastError = null;
|
||||
for (const baseUrl of apiUrls) {
|
||||
try {
|
||||
console.log(`Bootstrap: Attempting connection to ${baseUrl}...`);
|
||||
// --- STEP 1: Get Device Config ---
|
||||
const deviceUrl = `${baseUrl}/v3/crud/event_device/${seed.event_device_id}`;
|
||||
const deviceResponse = await fetch(deviceUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-aether-api-key': seed.aether_api_key,
|
||||
'x-no-account-id': 'Nothing to See Here'
|
||||
},
|
||||
});
|
||||
if (!deviceResponse.ok) {
|
||||
throw new Error(`Device lookup failed (${deviceResponse.status})`);
|
||||
}
|
||||
const deviceResult = await deviceResponse.json();
|
||||
const deviceData = deviceResult.data || deviceResult;
|
||||
// Use 'app_base_url' as the FQDN for the site lookup
|
||||
const fqdn = deviceData.app_base_url || 'native-demo.oneskyit.com';
|
||||
// --- STEP 2: Get Site Context ---
|
||||
const searchUrl = `${baseUrl}/v3/crud/site_domain/search`;
|
||||
const siteResponse = await fetch(searchUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-aether-api-key': seed.aether_api_key,
|
||||
'x-no-account-id': 'Nothing to See Here',
|
||||
'x-account-id': deviceData.account_id_random || deviceData.account_id || ''
|
||||
},
|
||||
body: JSON.stringify({
|
||||
search_query: {
|
||||
and: [{ field: 'fqdn', op: 'eq', value: fqdn }]
|
||||
},
|
||||
limit: 1
|
||||
})
|
||||
});
|
||||
if (!siteResponse.ok) {
|
||||
throw new Error(`Site context lookup failed (${siteResponse.status})`);
|
||||
}
|
||||
const siteResult = await siteResponse.json();
|
||||
const siteDomain = (siteResult.data && siteResult.data.length > 0) ? siteResult.data[0] : null;
|
||||
console.log(`Bootstrap Success using ${baseUrl}`);
|
||||
return {
|
||||
...siteDomain,
|
||||
native_device: deviceData,
|
||||
aether_api_key: seed.aether_api_key // Include the key for frontend use
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
console.warn(`Bootstrap failed for ${baseUrl}: `, error);
|
||||
lastError = error;
|
||||
continue; // Try next URL
|
||||
}
|
||||
}
|
||||
console.error('Bootstrap Critical Failure: All API endpoints exhausted.', lastError);
|
||||
return null;
|
||||
}
|
||||
//# sourceMappingURL=api_client.js.map
|
||||
1
dist/main/api_client.js.map
vendored
Normal file
1
dist/main/api_client.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"api_client.js","sourceRoot":"","sources":["../../src/main/api_client.ts"],"names":[],"mappings":";;AAEA,0CA4EC;AA5EM,KAAK,UAAU,eAAe,CAAC,IAAgB;IACpD,MAAM,OAAO,GAAG;QACd,IAAI,CAAC,mBAAmB;QACxB,IAAI,CAAC,oBAAoB;QACzB,IAAI,CAAC,mBAAmB;KACzB,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,SAAS,CAAa,CAAC;IAE/D,IAAI,SAAS,GAAQ,IAAI,CAAC;IAE1B,KAAK,MAAM,OAAO,IAAI,OAAO,EAAE,CAAC;QAC9B,IAAI,CAAC;YACH,OAAO,CAAC,GAAG,CAAC,uCAAuC,OAAO,KAAK,CAAC,CAAC;YAEjE,oCAAoC;YACpC,MAAM,SAAS,GAAG,GAAG,OAAO,yBAAyB,IAAI,CAAC,eAAe,EAAE,CAAC;YAC5E,MAAM,cAAc,GAAG,MAAM,KAAK,CAAC,SAAS,EAAE;gBAC5C,MAAM,EAAE,KAAK;gBACb,OAAO,EAAE;oBACP,cAAc,EAAE,kBAAkB;oBAClC,kBAAkB,EAAE,IAAI,CAAC,cAAc;oBACvC,iBAAiB,EAAE,qBAAqB;iBACzC;aACF,CAAC,CAAC;YAEH,IAAI,CAAC,cAAc,CAAC,EAAE,EAAE,CAAC;gBACvB,MAAM,IAAI,KAAK,CAAC,yBAAyB,cAAc,CAAC,MAAM,GAAG,CAAC,CAAC;YACrE,CAAC;YAED,MAAM,YAAY,GAAG,MAAM,cAAc,CAAC,IAAI,EAAE,CAAC;YACjD,MAAM,UAAU,GAAG,YAAY,CAAC,IAAI,IAAI,YAAY,CAAC;YAErD,qDAAqD;YACrD,MAAM,IAAI,GAAG,UAAU,CAAC,YAAY,IAAI,0BAA0B,CAAC;YAEnE,mCAAmC;YACnC,MAAM,SAAS,GAAG,GAAG,OAAO,6BAA6B,CAAC;YAC1D,MAAM,YAAY,GAAG,MAAM,KAAK,CAAC,SAAS,EAAE;gBAC1C,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACP,cAAc,EAAE,kBAAkB;oBAClC,kBAAkB,EAAE,IAAI,CAAC,cAAc;oBACvC,iBAAiB,EAAE,qBAAqB;oBACxC,cAAc,EAAE,UAAU,CAAC,iBAAiB,IAAI,UAAU,CAAC,UAAU,IAAI,EAAE;iBAC5E;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;oBACnB,YAAY,EAAE;wBACZ,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;qBAChD;oBACD,KAAK,EAAE,CAAC;iBACT,CAAC;aACH,CAAC,CAAC;YAEH,IAAI,CAAC,YAAY,CAAC,EAAE,EAAE,CAAC;gBACrB,MAAM,IAAI,KAAK,CAAC,+BAA+B,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC;YACzE,CAAC;YAED,MAAM,UAAU,GAAG,MAAM,YAAY,CAAC,IAAI,EAAE,CAAC;YAC7C,MAAM,UAAU,GAAG,CAAC,UAAU,CAAC,IAAI,IAAI,UAAU,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YAE/F,OAAO,CAAC,GAAG,CAAC,2BAA2B,OAAO,EAAE,CAAC,CAAC;YAElD,OAAO;gBACL,GAAG,UAAU;gBACb,aAAa,EAAE,UAAU;gBACzB,cAAc,EAAE,IAAI,CAAC,cAAc,CAAC,mCAAmC;aACxE,CAAC;QAEJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,CAAC,wBAAwB,OAAO,IAAI,EAAE,KAAK,CAAC,CAAC;YACzD,SAAS,GAAG,KAAK,CAAC;YAClB,SAAS,CAAC,eAAe;QAC3B,CAAC;IACH,CAAC;IAED,OAAO,CAAC,KAAK,CAAC,0DAA0D,EAAE,SAAS,CAAC,CAAC;IACrF,OAAO,IAAI,CAAC;AACd,CAAC"}
|
||||
62
dist/main/config_loader.js
vendored
Normal file
62
dist/main/config_loader.js
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.loadSeedConfig = loadSeedConfig;
|
||||
const fs = __importStar(require("fs"));
|
||||
const path = __importStar(require("path"));
|
||||
const os = __importStar(require("os"));
|
||||
async function loadSeedConfig() {
|
||||
// For development, we look in the home directory
|
||||
const configPath = path.join(os.homedir(), 'seed.json');
|
||||
try {
|
||||
if (!fs.existsSync(configPath)) {
|
||||
console.log(`Seed config not found at: ${configPath}`);
|
||||
return null;
|
||||
}
|
||||
const data = fs.readFileSync(configPath, 'utf-8');
|
||||
const config = JSON.parse(data);
|
||||
// Basic validation
|
||||
if (!config.event_device_id) {
|
||||
console.error('Invalid seed config: missing event_device_id');
|
||||
return null;
|
||||
}
|
||||
return config;
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error loading seed config:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=config_loader.js.map
|
||||
1
dist/main/config_loader.js.map
vendored
Normal file
1
dist/main/config_loader.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"config_loader.js","sourceRoot":"","sources":["../../src/main/config_loader.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAKA,wCAwBC;AA7BD,uCAAyB;AACzB,2CAA6B;AAC7B,uCAAyB;AAGlB,KAAK,UAAU,cAAc;IAClC,iDAAiD;IACjD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,WAAW,CAAC,CAAC;IAExD,IAAI,CAAC;QACH,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC/B,OAAO,CAAC,GAAG,CAAC,6BAA6B,UAAU,EAAE,CAAC,CAAC;YACvD,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,IAAI,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QAClD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAe,CAAC;QAE9C,mBAAmB;QACnB,IAAI,CAAC,MAAM,CAAC,eAAe,EAAE,CAAC;YAC5B,OAAO,CAAC,KAAK,CAAC,8CAA8C,CAAC,CAAC;YAC9D,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,4BAA4B,EAAE,KAAK,CAAC,CAAC;QACnD,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|
||||
218
dist/main/file_handlers.js
vendored
Normal file
218
dist/main/file_handlers.js
vendored
Normal file
@@ -0,0 +1,218 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.registerFileHandlers = registerFileHandlers;
|
||||
const electron_1 = require("electron");
|
||||
const fs = __importStar(require("fs"));
|
||||
const path = __importStar(require("path"));
|
||||
const os = __importStar(require("os"));
|
||||
const crypto = __importStar(require("crypto"));
|
||||
const child_process_1 = require("child_process");
|
||||
const axios_1 = __importDefault(require("axios"));
|
||||
const file_utils_1 = require("./file_utils");
|
||||
let endpoints_in_progress = [];
|
||||
function registerFileHandlers() {
|
||||
// Flexible organization: [root]/[prefix_len-char-prefix]/[hash].file
|
||||
function get_organized_hashed_path(root, hash, prefix_len = 2) {
|
||||
const expanded_root = (0, file_utils_1.expandPath)(root);
|
||||
const prefix = hash.substring(0, Math.max(1, Math.min(prefix_len, 8)));
|
||||
const dir = path.join(expanded_root, prefix);
|
||||
if (!fs.existsSync(dir))
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
return path.join(dir, `${hash}.file`);
|
||||
}
|
||||
electron_1.ipcMain.handle('native:check-cache', async (event, { cache_root, hash, hash_prefix_length = 2, verify_hash = false }) => {
|
||||
const full_path = get_organized_hashed_path(cache_root, hash, hash_prefix_length);
|
||||
if (!fs.existsSync(full_path))
|
||||
return false;
|
||||
if (verify_hash) {
|
||||
try {
|
||||
const file_buffer = fs.readFileSync(full_path);
|
||||
const actual_hash = crypto.createHash('sha256').update(file_buffer).digest('hex');
|
||||
return actual_hash === hash;
|
||||
}
|
||||
catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
electron_1.ipcMain.handle('native:download-to-cache', async (event, { url, cache_root, hash, api_key, account_id, hash_prefix_length = 2 }) => {
|
||||
const full_path = get_organized_hashed_path(cache_root, hash, hash_prefix_length);
|
||||
const tmp_path = `${full_path}.tmp`;
|
||||
if (endpoints_in_progress.includes(url))
|
||||
return { success: true, status: 'in_progress' };
|
||||
// 1. If final file exists, skip
|
||||
if (fs.existsSync(full_path))
|
||||
return { success: true, path: full_path, status: 'exists' };
|
||||
// 2. Handle stale .tmp files (Legacy "Trust No One" pattern)
|
||||
if (fs.existsSync(tmp_path)) {
|
||||
const stats = fs.statSync(tmp_path);
|
||||
const age_ms = Date.now() - stats.mtimeMs;
|
||||
// If the tmp file is older than 5 minutes, assume previous download crashed and delete it
|
||||
if (age_ms > 5 * 60 * 1000) {
|
||||
console.log(`Native: Deleting stale temp file (${Math.round(age_ms / 1000)}s old)`);
|
||||
fs.unlinkSync(tmp_path);
|
||||
}
|
||||
else {
|
||||
return { success: true, status: 'in_progress', detail: 'fresh_tmp_exists' };
|
||||
}
|
||||
}
|
||||
console.log(`Native: Hardened Download -> ${full_path}`);
|
||||
try {
|
||||
endpoints_in_progress.push(url);
|
||||
const response = await (0, axios_1.default)({
|
||||
method: 'get', url, responseType: 'stream',
|
||||
headers: {
|
||||
'x-aether-api-key': api_key,
|
||||
'x-account-id': account_id || '',
|
||||
'x-no-account-id': 'Nothing to See Here'
|
||||
}
|
||||
});
|
||||
const writer = fs.createWriteStream(tmp_path);
|
||||
response.data.pipe(writer);
|
||||
await new Promise((resolve, reject) => {
|
||||
writer.on('finish', () => resolve());
|
||||
writer.on('error', reject);
|
||||
});
|
||||
// 3. Verify Integrity before renaming (The "Trust No One" Check)
|
||||
const file_buffer = fs.readFileSync(tmp_path);
|
||||
const actual_hash = crypto.createHash('sha256').update(file_buffer).digest('hex');
|
||||
if (actual_hash !== hash) {
|
||||
console.error(`Native: Hash Mismatch! Expected ${hash}, got ${actual_hash}`);
|
||||
fs.unlinkSync(tmp_path);
|
||||
return { success: false, error: 'Integrity check failed: Hash mismatch' };
|
||||
}
|
||||
fs.renameSync(tmp_path, full_path);
|
||||
console.log(`Native: Cache Integrity Verified. File moved to final destination.`);
|
||||
return { success: true, path: full_path };
|
||||
}
|
||||
catch (error) {
|
||||
if (fs.existsSync(tmp_path))
|
||||
fs.unlinkSync(tmp_path);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
finally {
|
||||
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 }) => {
|
||||
try {
|
||||
const source = get_organized_hashed_path(cache_root, hash, hash_prefix_length);
|
||||
const expanded_temp = (0, file_utils_1.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) => {
|
||||
(0, child_process_1.exec)(`libreoffice --impress "${target}"`, (err) => {
|
||||
if (err)
|
||||
resolve({ success: false, error: err.message });
|
||||
else
|
||||
resolve({ success: true });
|
||||
});
|
||||
});
|
||||
}
|
||||
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 1
|
||||
run slide show of active presentation
|
||||
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 };
|
||||
}
|
||||
catch (error) {
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
}
|
||||
//# sourceMappingURL=file_handlers.js.map
|
||||
1
dist/main/file_handlers.js.map
vendored
Normal file
1
dist/main/file_handlers.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
53
dist/main/file_utils.js
vendored
Normal file
53
dist/main/file_utils.js
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.expandPath = expandPath;
|
||||
exports.getHashedPath = getHashedPath;
|
||||
const os = __importStar(require("os"));
|
||||
const path = __importStar(require("path"));
|
||||
function expandPath(filePath) {
|
||||
if (!filePath)
|
||||
return filePath;
|
||||
// Resolve all instances of [home] and [tmp] using global regex
|
||||
return filePath
|
||||
.replace(/\[home\]/g, os.homedir())
|
||||
.replace(/\[tmp\]/g, os.tmpdir());
|
||||
}
|
||||
function getHashedPath(cacheRoot, hash) {
|
||||
const expandedRoot = expandPath(cacheRoot);
|
||||
const subdirectory = hash.substring(0, 2);
|
||||
return path.join(expandedRoot, subdirectory, `${hash}.file`);
|
||||
}
|
||||
//# sourceMappingURL=file_utils.js.map
|
||||
1
dist/main/file_utils.js.map
vendored
Normal file
1
dist/main/file_utils.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"file_utils.js","sourceRoot":"","sources":["../../src/main/file_utils.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAGA,gCAMC;AAED,sCAIC;AAfD,uCAAyB;AACzB,2CAA6B;AAE7B,SAAgB,UAAU,CAAC,QAAgB;IACzC,IAAI,CAAC,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC/B,+DAA+D;IAC/D,OAAO,QAAQ;SACZ,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,OAAO,EAAE,CAAC;SAClC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC;AACtC,CAAC;AAED,SAAgB,aAAa,CAAC,SAAiB,EAAE,IAAY;IAC3D,MAAM,YAAY,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC;IAC3C,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC1C,OAAO,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,YAAY,EAAE,GAAG,IAAI,OAAO,CAAC,CAAC;AAC/D,CAAC"}
|
||||
120
dist/main/index.js
vendored
Normal file
120
dist/main/index.js
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const electron_1 = require("electron");
|
||||
const path = __importStar(require("path"));
|
||||
const os = __importStar(require("os"));
|
||||
const config_loader_1 = require("./config_loader");
|
||||
const api_client_1 = require("./api_client");
|
||||
const shell_handlers_1 = require("./shell_handlers");
|
||||
const file_handlers_1 = require("./file_handlers");
|
||||
const system_handlers_1 = require("./system_handlers");
|
||||
let mainWindow = null;
|
||||
let cachedSeed = null;
|
||||
let cachedFullConfig = null;
|
||||
async function createWindow() {
|
||||
cachedSeed = await (0, config_loader_1.loadSeedConfig)();
|
||||
if (cachedSeed) {
|
||||
cachedFullConfig = await (0, api_client_1.fetchFullConfig)(cachedSeed);
|
||||
}
|
||||
mainWindow = new electron_1.BrowserWindow({
|
||||
width: 1600,
|
||||
height: 900,
|
||||
title: 'OSIT Aether Launcher (Native)',
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, '../preload/index.js'),
|
||||
contextIsolation: true,
|
||||
nodeIntegration: false,
|
||||
},
|
||||
});
|
||||
let targetUrl = 'http://demo.localhost:5173';
|
||||
if (cachedFullConfig && cachedFullConfig.native_device) {
|
||||
const device = cachedFullConfig.native_device;
|
||||
const eventId = device.event_id_random || device.event_id;
|
||||
const locationId = device.event_location_id_random || device.event_location_id || '';
|
||||
// Use app_base_url from the device record (e.g. bgh.oneskyit.com).
|
||||
// Fall back to localhost only if nothing is configured — never override a real domain.
|
||||
const host = device.app_base_url || 'demo.localhost:5173';
|
||||
// Use https for real domains; localhost dev URLs stay on http
|
||||
const protocol = host.includes('localhost') ? 'http' : 'https';
|
||||
targetUrl = `${protocol}://${host}/events/${eventId}/launcher/${locationId}`;
|
||||
}
|
||||
// Only open DevTools in development (not in a packaged .app build)
|
||||
if (!electron_1.app.isPackaged)
|
||||
mainWindow.webContents.openDevTools();
|
||||
mainWindow.loadURL(targetUrl).catch(() => {
|
||||
mainWindow?.loadURL('https://dev-demo.oneskyit.com/');
|
||||
});
|
||||
mainWindow.on('closed', () => { mainWindow = null; });
|
||||
}
|
||||
(0, shell_handlers_1.registerShellHandlers)();
|
||||
(0, file_handlers_1.registerFileHandlers)();
|
||||
(0, system_handlers_1.registerSystemHandlers)();
|
||||
electron_1.app.on('ready', createWindow);
|
||||
electron_1.app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin')
|
||||
electron_1.app.quit();
|
||||
});
|
||||
electron_1.app.on('activate', () => {
|
||||
if (mainWindow === null)
|
||||
createWindow();
|
||||
});
|
||||
electron_1.ipcMain.handle('get-seed-config', async () => cachedSeed || await (0, config_loader_1.loadSeedConfig)());
|
||||
electron_1.ipcMain.handle('get-device-config', async () => cachedFullConfig);
|
||||
electron_1.ipcMain.handle('get-jwt', async () => null);
|
||||
electron_1.ipcMain.handle('get-device-info', async () => {
|
||||
const interfaces = os.networkInterfaces();
|
||||
const addresses = [];
|
||||
for (const name of Object.keys(interfaces)) {
|
||||
for (const net of interfaces[name]) {
|
||||
if (net.family === 'IPv4' && !net.internal) {
|
||||
addresses.push(net.address);
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
platform: os.platform(),
|
||||
release: os.release(),
|
||||
arch: os.arch(),
|
||||
hostname: os.hostname(),
|
||||
cpus: os.cpus().length,
|
||||
total_mem: os.totalmem(),
|
||||
free_mem: os.freemem(),
|
||||
ip_addresses: addresses,
|
||||
home_directory: os.homedir(),
|
||||
tmp_directory: os.tmpdir()
|
||||
};
|
||||
});
|
||||
//# sourceMappingURL=index.js.map
|
||||
1
dist/main/index.js.map
vendored
Normal file
1
dist/main/index.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/main/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,uCAAuD;AACvD,2CAA6B;AAC7B,uCAAyB;AACzB,mDAAiD;AACjD,6CAA+C;AAC/C,qDAAyD;AACzD,mDAAuD;AACvD,uDAA2D;AAG3D,IAAI,UAAU,GAAyB,IAAI,CAAC;AAC5C,IAAI,UAAU,GAAsB,IAAI,CAAC;AACzC,IAAI,gBAAgB,GAAQ,IAAI,CAAC;AAEjC,KAAK,UAAU,YAAY;IACzB,UAAU,GAAG,MAAM,IAAA,8BAAc,GAAE,CAAC;IACpC,IAAI,UAAU,EAAE,CAAC;QACf,gBAAgB,GAAG,MAAM,IAAA,4BAAe,EAAC,UAAU,CAAC,CAAC;IACvD,CAAC;IAED,UAAU,GAAG,IAAI,wBAAa,CAAC;QAC7B,KAAK,EAAE,IAAI;QACX,MAAM,EAAE,GAAG;QACX,KAAK,EAAE,+BAA+B;QACtC,cAAc,EAAE;YACd,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,qBAAqB,CAAC;YACpD,gBAAgB,EAAE,IAAI;YACtB,eAAe,EAAE,KAAK;SACvB;KACF,CAAC,CAAC;IAEH,IAAI,SAAS,GAAG,4BAA4B,CAAC;IAC7C,IAAI,gBAAgB,IAAI,gBAAgB,CAAC,aAAa,EAAE,CAAC;QACvD,MAAM,MAAM,GAAG,gBAAgB,CAAC,aAAa,CAAC;QAC9C,MAAM,OAAO,GAAG,MAAM,CAAC,eAAe,IAAI,MAAM,CAAC,QAAQ,CAAC;QAC1D,MAAM,UAAU,GAAG,MAAM,CAAC,wBAAwB,IAAI,MAAM,CAAC,iBAAiB,IAAI,EAAE,CAAC;QACrF,mEAAmE;QACnE,uFAAuF;QACvF,MAAM,IAAI,GAAG,MAAM,CAAC,YAAY,IAAI,qBAAqB,CAAC;QAC1D,8DAA8D;QAC9D,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;QAC/D,SAAS,GAAG,GAAG,QAAQ,MAAM,IAAI,WAAW,OAAO,aAAa,UAAU,EAAE,CAAC;IAC/E,CAAC;IAED,mEAAmE;IACnE,IAAI,CAAC,cAAG,CAAC,UAAU;QAAE,UAAU,CAAC,WAAW,CAAC,YAAY,EAAE,CAAC;IAC3D,UAAU,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;QACvC,UAAU,EAAE,OAAO,CAAC,gCAAgC,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE,GAAG,UAAU,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AACxD,CAAC;AAED,IAAA,sCAAqB,GAAE,CAAC;AACxB,IAAA,oCAAoB,GAAE,CAAC;AACvB,IAAA,wCAAsB,GAAE,CAAC;AAEzB,cAAG,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;AAE9B,cAAG,CAAC,EAAE,CAAC,mBAAmB,EAAE,GAAG,EAAE;IAC/B,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ;QAAE,cAAG,CAAC,IAAI,EAAE,CAAC;AAChD,CAAC,CAAC,CAAC;AAEH,cAAG,CAAC,EAAE,CAAC,UAAU,EAAE,GAAG,EAAE;IACtB,IAAI,UAAU,KAAK,IAAI;QAAE,YAAY,EAAE,CAAC;AAC1C,CAAC,CAAC,CAAC;AAEH,kBAAO,CAAC,MAAM,CAAC,iBAAiB,EAAE,KAAK,IAAI,EAAE,CAAC,UAAU,IAAI,MAAM,IAAA,8BAAc,GAAE,CAAC,CAAC;AACpF,kBAAO,CAAC,MAAM,CAAC,mBAAmB,EAAE,KAAK,IAAI,EAAE,CAAC,gBAAgB,CAAC,CAAC;AAClE,kBAAO,CAAC,MAAM,CAAC,SAAS,EAAE,KAAK,IAAI,EAAE,CAAC,IAAI,CAAC,CAAC;AAE5C,kBAAO,CAAC,MAAM,CAAC,iBAAiB,EAAE,KAAK,IAAI,EAAE;IAC3C,MAAM,UAAU,GAAG,EAAE,CAAC,iBAAiB,EAAE,CAAC;IAC1C,MAAM,SAAS,GAAa,EAAE,CAAC;IAC/B,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;QAC3C,KAAK,MAAM,GAAG,IAAI,UAAU,CAAC,IAAI,CAAE,EAAE,CAAC;YACpC,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;gBAC3C,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YAC9B,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO;QACL,QAAQ,EAAE,EAAE,CAAC,QAAQ,EAAE;QACvB,OAAO,EAAE,EAAE,CAAC,OAAO,EAAE;QACrB,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE;QACf,QAAQ,EAAE,EAAE,CAAC,QAAQ,EAAE;QACvB,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,MAAM;QACtB,SAAS,EAAE,EAAE,CAAC,QAAQ,EAAE;QACxB,QAAQ,EAAE,EAAE,CAAC,OAAO,EAAE;QACtB,YAAY,EAAE,SAAS;QACvB,cAAc,EAAE,EAAE,CAAC,OAAO,EAAE;QAC5B,aAAa,EAAE,EAAE,CAAC,MAAM,EAAE;KAC3B,CAAC;AACJ,CAAC,CAAC,CAAC"}
|
||||
258
dist/main/shell_handlers.js
vendored
Normal file
258
dist/main/shell_handlers.js
vendored
Normal file
@@ -0,0 +1,258 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.registerShellHandlers = registerShellHandlers;
|
||||
const electron_1 = require("electron");
|
||||
const child_process_1 = require("child_process");
|
||||
const os = __importStar(require("os"));
|
||||
const file_utils_1 = require("./file_utils");
|
||||
function registerShellHandlers() {
|
||||
electron_1.ipcMain.handle('native:open-folder', async (event, folderPath) => {
|
||||
const cleanPath = (0, file_utils_1.expandPath)(folderPath);
|
||||
const error = await electron_1.shell.openPath(cleanPath);
|
||||
return { success: !error, error };
|
||||
});
|
||||
electron_1.ipcMain.handle('native:run-cmd', async (event, { cmd, timeout = 30000 }) => {
|
||||
const cleanCmd = (0, file_utils_1.expandPath)(cmd);
|
||||
return new Promise((resolve) => {
|
||||
(0, child_process_1.exec)(cleanCmd, { timeout }, (error, stdout, stderr) => {
|
||||
resolve({ success: !error, stdout: stdout.trim(), stderr: stderr.trim(), error: error ? error.message : null });
|
||||
});
|
||||
});
|
||||
});
|
||||
electron_1.ipcMain.handle('native:run-cmd-sync', async (event, { cmd }) => {
|
||||
const cleanCmd = (0, file_utils_1.expandPath)(cmd);
|
||||
try {
|
||||
const stdout = (0, child_process_1.execSync)(cleanCmd).toString();
|
||||
return { success: true, stdout: stdout.trim() };
|
||||
}
|
||||
catch (error) {
|
||||
return { success: false, error: error.message, stderr: error.stderr?.toString() };
|
||||
}
|
||||
});
|
||||
electron_1.ipcMain.handle('native:run-osascript', async (event, script) => {
|
||||
if (os.platform() !== 'darwin')
|
||||
return { success: false, error: 'AppleScript is only available on macOS' };
|
||||
const escapedScript = script.replace(/"/g, '\"');
|
||||
const cmd = `osascript -e "${escapedScript}"`;
|
||||
return new Promise((resolve) => {
|
||||
(0, child_process_1.exec)(cmd, (error, stdout, stderr) => {
|
||||
resolve({ success: !error, stdout: stdout.trim(), stderr: stderr.trim(), error: error ? error.message : null });
|
||||
});
|
||||
});
|
||||
});
|
||||
electron_1.ipcMain.handle('native:kill-processes', async (event, { process_name_li = [] }) => {
|
||||
console.log(`Native: Killing processes -> `, process_name_li);
|
||||
const results = [];
|
||||
for (const name of process_name_li) {
|
||||
const cmd = os.platform() === 'win32'
|
||||
? `taskkill /F /IM ${name} /T`
|
||||
: `pkill -f ${name}`;
|
||||
try {
|
||||
(0, child_process_1.execSync)(cmd);
|
||||
results.push({ name, success: true });
|
||||
}
|
||||
catch (e) {
|
||||
results.push({ name, success: false, error: e.message });
|
||||
}
|
||||
}
|
||||
return { success: true, results };
|
||||
});
|
||||
electron_1.ipcMain.handle('native:open-local-file-v2', async (event, filePath) => {
|
||||
const cleanPath = (0, file_utils_1.expandPath)(filePath);
|
||||
const error = await electron_1.shell.openPath(cleanPath);
|
||||
return { success: !error, error };
|
||||
});
|
||||
electron_1.ipcMain.handle('native:launch-presentation', async (event, { path: rawPath, app: appType = 'default' }) => {
|
||||
const cleanedPath = (0, file_utils_1.expandPath)(rawPath);
|
||||
console.log(`Native: Launching Presentation -> ${cleanedPath} (App: ${appType})`);
|
||||
if (os.platform() === 'linux') {
|
||||
const cmd = `libreoffice --impress "${cleanedPath}"`;
|
||||
return new Promise((resolve) => {
|
||||
(0, child_process_1.exec)(cmd, (err, stdout, stderr) => {
|
||||
if (err)
|
||||
resolve({ success: false, error: err.message });
|
||||
else
|
||||
resolve({ success: true, stdout, stderr });
|
||||
});
|
||||
});
|
||||
}
|
||||
if (os.platform() === 'darwin') {
|
||||
let script = '';
|
||||
if (appType === 'keynote') {
|
||||
script = `
|
||||
tell application "Keynote"
|
||||
activate
|
||||
open (POSIX file "${cleanedPath}")
|
||||
delay 1
|
||||
start (front document)
|
||||
end tell
|
||||
`;
|
||||
}
|
||||
else if (appType === 'powerpoint') {
|
||||
script = `
|
||||
tell application "Microsoft PowerPoint"
|
||||
activate
|
||||
open (POSIX file "${cleanedPath}")
|
||||
delay 1
|
||||
run slide show of active presentation
|
||||
end tell
|
||||
`;
|
||||
}
|
||||
if (script) {
|
||||
return new Promise((resolve) => {
|
||||
const escapedScript = script.replace(/"/g, '\\"');
|
||||
(0, child_process_1.exec)(`osascript -e "${escapedScript}"`, (err, stdout, stderr) => {
|
||||
if (err)
|
||||
resolve({ success: false, error: err.message });
|
||||
else
|
||||
resolve({ success: true });
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
const error = await electron_1.shell.openPath(cleanedPath);
|
||||
return { success: !error, error };
|
||||
});
|
||||
electron_1.ipcMain.handle('native:control-presentation', async (event, { app, action }) => {
|
||||
if (os.platform() !== 'darwin')
|
||||
return { success: false, error: 'Presentation control is only available on macOS' };
|
||||
let script = '';
|
||||
if (app === 'powerpoint') {
|
||||
switch (action) {
|
||||
case 'next':
|
||||
script = 'tell application "Microsoft PowerPoint" to next slide of slide show view of active presentation';
|
||||
break;
|
||||
case 'prev':
|
||||
script = 'tell application "Microsoft PowerPoint" to previous slide of slide show view of active presentation';
|
||||
break;
|
||||
case 'start':
|
||||
script = 'tell application "Microsoft PowerPoint" to run slide show of active presentation';
|
||||
break;
|
||||
case 'stop':
|
||||
script = 'tell application "Microsoft PowerPoint" to stop slide show of active presentation';
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (app === 'keynote') {
|
||||
switch (action) {
|
||||
case 'next':
|
||||
script = 'tell application "Keynote" to show next';
|
||||
break;
|
||||
case 'prev':
|
||||
script = 'tell application "Keynote" to show previous';
|
||||
break;
|
||||
case 'start':
|
||||
script = 'tell application "Keynote" to start (front document)';
|
||||
break;
|
||||
case 'stop':
|
||||
script = 'tell application "Keynote" to stop';
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!script)
|
||||
return { success: false, error: `Unsupported app or action: ${app}/${action}` };
|
||||
return new Promise((resolve) => {
|
||||
(0, child_process_1.exec)(`osascript -e "${script.replace(/"/g, '\\"')}"`, (error, stdout, stderr) => {
|
||||
resolve({ success: !error, stdout: stdout.trim(), stderr: stderr.trim(), error: error ? error.message : null });
|
||||
});
|
||||
});
|
||||
});
|
||||
electron_1.ipcMain.handle('native:list-tools', async () => {
|
||||
return [
|
||||
{
|
||||
name: 'open_folder',
|
||||
description: 'Opens a directory in the OS file explorer (Finder/Files/Explorer).',
|
||||
params: { path: 'string' }
|
||||
},
|
||||
{
|
||||
name: 'run_cmd',
|
||||
description: 'Executes an asynchronous shell command with a timeout.',
|
||||
params: { cmd: 'string', timeout: 'number (optional)' }
|
||||
},
|
||||
{
|
||||
name: 'run_cmd_sync',
|
||||
description: 'Executes a synchronous shell command.',
|
||||
params: { cmd: 'string' }
|
||||
},
|
||||
{
|
||||
name: 'run_osascript',
|
||||
description: 'Executes a raw AppleScript string (macOS only).',
|
||||
params: { script: 'string' }
|
||||
},
|
||||
{
|
||||
name: 'kill_processes',
|
||||
description: 'Forcefully terminates processes by name.',
|
||||
params: { process_name_li: 'string[]' }
|
||||
},
|
||||
{
|
||||
name: 'open_local_file_v2',
|
||||
description: 'Opens a local file using the default OS handler.',
|
||||
params: { filePath: 'string' }
|
||||
},
|
||||
{
|
||||
name: 'launch_presentation',
|
||||
description: 'Phase 5: Specialized launcher for PowerPoint, Keynote, and LibreOffice with auto-focus.',
|
||||
params: { path: 'string', app: 'default|powerpoint|keynote' }
|
||||
},
|
||||
{
|
||||
name: 'control_presentation',
|
||||
description: 'Phase 5: Remote navigation for active slideshows.',
|
||||
params: { app: 'powerpoint|keynote', action: 'next|prev|start|stop' }
|
||||
},
|
||||
{
|
||||
name: 'check_cache',
|
||||
description: 'Checks if a file exists in the local organized cache.',
|
||||
params: { cache_root: 'string', hash: 'string', hash_prefix_length: 'number' }
|
||||
},
|
||||
{
|
||||
name: 'download_to_cache',
|
||||
description: 'Downloads a file from the API directly into the native cache.',
|
||||
params: { url: 'string', cache_root: 'string', hash: 'string', api_key: 'string', account_id: 'string' }
|
||||
},
|
||||
{
|
||||
name: 'launch_from_cache',
|
||||
description: 'Atomic operation: Copies file from cache to temp with original name and launches via specialized handler.',
|
||||
params: { cache_root: 'string', hash: 'string', temp_root: 'string', filename: 'string' }
|
||||
},
|
||||
{
|
||||
name: 'get_device_info',
|
||||
description: 'Returns hardware and OS metadata (CPUs, RAM, IP addresses, Hostname).',
|
||||
params: {}
|
||||
}
|
||||
];
|
||||
});
|
||||
}
|
||||
//# sourceMappingURL=shell_handlers.js.map
|
||||
1
dist/main/shell_handlers.js.map
vendored
Normal file
1
dist/main/shell_handlers.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
293
dist/main/system_handlers.js
vendored
Normal file
293
dist/main/system_handlers.js
vendored
Normal file
@@ -0,0 +1,293 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.registerSystemHandlers = registerSystemHandlers;
|
||||
const electron_1 = require("electron");
|
||||
const os = __importStar(require("os"));
|
||||
const path = __importStar(require("path"));
|
||||
const fs = __importStar(require("fs"));
|
||||
const child_process_1 = require("child_process");
|
||||
const axios_1 = __importDefault(require("axios"));
|
||||
const file_utils_1 = require("./file_utils");
|
||||
// Helper to execute shell commands
|
||||
const runExec = (cmd) => {
|
||||
return new Promise((resolve) => {
|
||||
(0, child_process_1.exec)(cmd, (error, stdout, stderr) => {
|
||||
resolve({
|
||||
success: !error,
|
||||
stdout: stdout.trim(),
|
||||
stderr: stderr.trim(),
|
||||
error: error ? error.message : undefined
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
let recordingProcess = null;
|
||||
function registerSystemHandlers() {
|
||||
// 1. Window Control
|
||||
electron_1.ipcMain.handle('native:window-control', async (event, { action, value }) => {
|
||||
const win = electron_1.BrowserWindow.fromWebContents(event.sender);
|
||||
if (!win)
|
||||
return { success: false, error: 'No window found' };
|
||||
switch (action) {
|
||||
case 'maximize':
|
||||
win.maximize();
|
||||
break;
|
||||
case 'unmaximize':
|
||||
win.unmaximize();
|
||||
break;
|
||||
case 'minimize':
|
||||
win.minimize();
|
||||
break;
|
||||
case 'restore':
|
||||
win.restore();
|
||||
break;
|
||||
case 'close':
|
||||
win.close();
|
||||
break;
|
||||
case 'devtools':
|
||||
if (value)
|
||||
win.webContents.openDevTools();
|
||||
else
|
||||
win.webContents.closeDevTools();
|
||||
break;
|
||||
case 'kiosk':
|
||||
win.setKiosk(!!value);
|
||||
break;
|
||||
case 'fullscreen':
|
||||
win.setFullScreen(!!value);
|
||||
break;
|
||||
case 'reload':
|
||||
win.reload();
|
||||
break;
|
||||
default: return { success: false, error: `Unknown action: ${action}` };
|
||||
}
|
||||
return { success: true };
|
||||
});
|
||||
// 2. Set Wallpaper
|
||||
electron_1.ipcMain.handle('native:set-wallpaper', async (event, { path: imagePath }) => {
|
||||
const cleanPath = (0, file_utils_1.expandPath)(imagePath);
|
||||
if (!fs.existsSync(cleanPath))
|
||||
return { success: false, error: 'Image file not found' };
|
||||
if (os.platform() === 'darwin') {
|
||||
const script = `tell application "System Events" to set picture of every desktop to "${cleanPath}"`;
|
||||
return await runExec(`osascript -e '${script}'`);
|
||||
}
|
||||
else if (os.platform() === 'linux') {
|
||||
// Gnome/Ubuntu default
|
||||
return await runExec(`gsettings set org.gnome.desktop.background picture-uri "file://${cleanPath}"`);
|
||||
}
|
||||
return { success: false, error: 'Platform not supported' };
|
||||
});
|
||||
// 3. Power Control
|
||||
electron_1.ipcMain.handle('native:power-control', async (event, { action }) => {
|
||||
let cmd = '';
|
||||
const isMac = os.platform() === 'darwin';
|
||||
const isLinux = os.platform() === 'linux';
|
||||
if (action === 'shutdown') {
|
||||
if (isMac)
|
||||
cmd = 'shutdown -h now'; // Requires sudo/admin usually
|
||||
if (isLinux)
|
||||
cmd = 'shutdown -h now';
|
||||
}
|
||||
else if (action === 'reboot') {
|
||||
if (isMac)
|
||||
cmd = 'shutdown -r now';
|
||||
if (isLinux)
|
||||
cmd = 'reboot';
|
||||
}
|
||||
else if (action === 'sleep') {
|
||||
if (isMac)
|
||||
cmd = 'pmset sleepnow';
|
||||
if (isLinux)
|
||||
cmd = 'systemctl suspend';
|
||||
}
|
||||
if (!cmd)
|
||||
return { success: false, error: 'Action not supported' };
|
||||
// NOTE: These commands often require root.
|
||||
// For a kiosk, you might configure sudoers to allow this specific command without password.
|
||||
return await runExec(cmd);
|
||||
});
|
||||
// 4. Open External (Browser)
|
||||
electron_1.ipcMain.handle('native:open-external', async (event, { url, app: appName }) => {
|
||||
if (appName === 'chrome') {
|
||||
if (os.platform() === 'darwin') {
|
||||
return await runExec(`open -a "Google Chrome" "${url}"`);
|
||||
}
|
||||
else if (os.platform() === 'linux') {
|
||||
return await runExec(`google-chrome "${url}"`);
|
||||
}
|
||||
}
|
||||
else if (appName === 'firefox') {
|
||||
if (os.platform() === 'darwin') {
|
||||
return await runExec(`open -a "Firefox" "${url}"`);
|
||||
}
|
||||
else if (os.platform() === 'linux') {
|
||||
return await runExec(`firefox "${url}"`);
|
||||
}
|
||||
}
|
||||
// Default system handler
|
||||
await electron_1.shell.openExternal(url);
|
||||
return { success: true };
|
||||
});
|
||||
// 5. Manage Recording (Aperture Wrapper)
|
||||
electron_1.ipcMain.handle('native:manage-recording', async (event, { action, options }) => {
|
||||
if (os.platform() !== 'darwin')
|
||||
return { success: false, error: 'Recording only supported on macOS' };
|
||||
// Path to bundled aperture binary
|
||||
// In dev: ./resources/bin/aperture
|
||||
// In prod: process.resourcesPath/bin/aperture
|
||||
const binPath = electron_1.app.isPackaged
|
||||
? path.join(process.resourcesPath, 'bin', 'aperture')
|
||||
: path.join(__dirname, '../../resources/bin/aperture'); // Adjust based on structure
|
||||
if (action === 'start') {
|
||||
if (recordingProcess)
|
||||
return { success: false, error: 'Recording already in progress' };
|
||||
const { fps = 30, audioDeviceId, output } = options || {};
|
||||
const cleanOutput = (0, file_utils_1.expandPath)(output || '~/tmp/recording.mp4');
|
||||
const args = ['run', '--fps', fps, '--output', cleanOutput];
|
||||
if (audioDeviceId)
|
||||
args.push('--audio-device-id', audioDeviceId);
|
||||
// Spawn process
|
||||
// Note: aperture is a CLI tool. We might need 'aperture' node package or the binary.
|
||||
// Assuming binary usage here.
|
||||
try {
|
||||
console.log(`Starting recording: ${binPath} ${args.join(' ')}`);
|
||||
recordingProcess = (0, child_process_1.spawn)(binPath, args);
|
||||
recordingProcess.on('error', (err) => {
|
||||
console.error('Recording error:', err);
|
||||
recordingProcess = null;
|
||||
});
|
||||
recordingProcess.on('exit', (code) => {
|
||||
console.log(`Recording exited with code ${code}`);
|
||||
recordingProcess = null;
|
||||
});
|
||||
return { success: true, pid: recordingProcess.pid };
|
||||
}
|
||||
catch (e) {
|
||||
return { success: false, error: e.message };
|
||||
}
|
||||
}
|
||||
else if (action === 'stop') {
|
||||
if (!recordingProcess)
|
||||
return { success: false, error: 'No recording in progress' };
|
||||
recordingProcess.kill('SIGINT'); // Send interrupt to stop cleanly
|
||||
recordingProcess = null;
|
||||
return { success: true };
|
||||
}
|
||||
else if (action === 'status') {
|
||||
return { success: true, isRecording: !!recordingProcess };
|
||||
}
|
||||
return { success: false, error: 'Unknown action' };
|
||||
});
|
||||
// 6. Set Display Layout (Displayplacer)
|
||||
electron_1.ipcMain.handle('native:set-display-layout', async (event, { mode, configStr }) => {
|
||||
if (os.platform() !== 'darwin')
|
||||
return { success: false, error: 'Display control only supported on macOS' };
|
||||
const binPath = electron_1.app.isPackaged
|
||||
? path.join(process.resourcesPath, 'bin', 'displayplacer')
|
||||
: path.join(__dirname, '../../resources/bin/displayplacer');
|
||||
let cmd = '';
|
||||
if (mode === 'mirror') {
|
||||
// This usually requires querying current IDs, which is complex.
|
||||
// If configStr is provided (output of 'displayplacer list'), use it.
|
||||
if (configStr) {
|
||||
cmd = `"${binPath}" ${configStr}`;
|
||||
}
|
||||
else {
|
||||
return { success: false, error: 'Config string required for now' };
|
||||
}
|
||||
}
|
||||
else if (mode === 'extend') {
|
||||
if (configStr) {
|
||||
cmd = `"${binPath}" ${configStr}`;
|
||||
}
|
||||
}
|
||||
if (cmd) {
|
||||
return await runExec(cmd);
|
||||
}
|
||||
return { success: false, error: 'Invalid mode or missing config' };
|
||||
});
|
||||
// 7. Update App
|
||||
electron_1.ipcMain.handle('native:update-app', async (event, { source, url, path: localPath }) => {
|
||||
// 1. Determine Source File
|
||||
let updateFile = '';
|
||||
const tempDir = os.tmpdir();
|
||||
const destName = 'update_package.zip'; // Or .app, .AppImage
|
||||
const destPath = path.join(tempDir, destName);
|
||||
if (source === 'url' && url) {
|
||||
// Download
|
||||
try {
|
||||
const response = await (0, axios_1.default)({
|
||||
method: 'get',
|
||||
url: url,
|
||||
responseType: 'stream'
|
||||
});
|
||||
const writer = fs.createWriteStream(destPath);
|
||||
response.data.pipe(writer);
|
||||
await new Promise((resolve, reject) => {
|
||||
writer.on('finish', () => resolve(true));
|
||||
writer.on('error', reject);
|
||||
});
|
||||
updateFile = destPath;
|
||||
}
|
||||
catch (e) {
|
||||
return { success: false, error: `Download failed: ${e.message}` };
|
||||
}
|
||||
}
|
||||
else if (source === 'file' && localPath) {
|
||||
const cleanLocal = (0, file_utils_1.expandPath)(localPath);
|
||||
if (fs.existsSync(cleanLocal)) {
|
||||
updateFile = cleanLocal;
|
||||
}
|
||||
else {
|
||||
return { success: false, error: 'Local update file not found' };
|
||||
}
|
||||
}
|
||||
if (!updateFile)
|
||||
return { success: false, error: 'No update source provided' };
|
||||
// 2. Install Logic (Stub)
|
||||
// Real implementation depends on OS and packaging format.
|
||||
// macOS: Mount DMG, copy .app to /Applications? Or Unzip .app?
|
||||
// Linux: chmod +x AppImage and move?
|
||||
console.log(`Ready to install update from: ${updateFile}`);
|
||||
// For now, just return success so the UI knows we "downloaded" it.
|
||||
return { success: true, message: 'Update downloaded/located. Installation logic requires packaging specifics.', downloadedPath: updateFile };
|
||||
});
|
||||
}
|
||||
//# sourceMappingURL=system_handlers.js.map
|
||||
1
dist/main/system_handlers.js.map
vendored
Normal file
1
dist/main/system_handlers.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
30
dist/preload/index.js
vendored
Normal file
30
dist/preload/index.js
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const electron_1 = require("electron");
|
||||
electron_1.contextBridge.exposeInMainWorld('aetherNative', {
|
||||
get_seed_config: () => electron_1.ipcRenderer.invoke('get-seed-config'),
|
||||
get_device_config: () => electron_1.ipcRenderer.invoke('get-device-config'),
|
||||
get_jwt: () => electron_1.ipcRenderer.invoke('get-jwt'),
|
||||
get_device_info: () => electron_1.ipcRenderer.invoke('get-device-info'),
|
||||
open_folder: (path) => electron_1.ipcRenderer.invoke('native:open-folder', path),
|
||||
run_cmd: (args) => electron_1.ipcRenderer.invoke('native:run-cmd', args),
|
||||
run_cmd_sync: (args) => electron_1.ipcRenderer.invoke('native:run-cmd-sync', args),
|
||||
run_osascript: (script) => electron_1.ipcRenderer.invoke('native:run-osascript', script),
|
||||
kill_processes: (args) => electron_1.ipcRenderer.invoke('native:kill-processes', args),
|
||||
open_local_file_v2: (path) => electron_1.ipcRenderer.invoke('native:open-local-file-v2', path),
|
||||
check_cache: (args) => electron_1.ipcRenderer.invoke('native:check-cache', args),
|
||||
download_to_cache: (args) => electron_1.ipcRenderer.invoke('native:download-to-cache', args),
|
||||
launch_from_cache: (args) => electron_1.ipcRenderer.invoke('native:launch-from-cache', args),
|
||||
launch_presentation: (args) => electron_1.ipcRenderer.invoke('native:launch-presentation', args),
|
||||
control_presentation: (args) => electron_1.ipcRenderer.invoke('native:control-presentation', args),
|
||||
list_tools: () => electron_1.ipcRenderer.invoke('native:list-tools'),
|
||||
// System Handlers (V5)
|
||||
set_wallpaper: (args) => electron_1.ipcRenderer.invoke('native:set-wallpaper', args),
|
||||
update_app: (args) => electron_1.ipcRenderer.invoke('native:update-app', args),
|
||||
window_control: (args) => electron_1.ipcRenderer.invoke('native:window-control', args),
|
||||
manage_recording: (args) => electron_1.ipcRenderer.invoke('native:manage-recording', args),
|
||||
set_display_layout: (args) => electron_1.ipcRenderer.invoke('native:set-display-layout', args),
|
||||
power_control: (args) => electron_1.ipcRenderer.invoke('native:power-control', args),
|
||||
open_external: (args) => electron_1.ipcRenderer.invoke('native:open-external', args),
|
||||
});
|
||||
//# sourceMappingURL=index.js.map
|
||||
1
dist/preload/index.js.map
vendored
Normal file
1
dist/preload/index.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/preload/index.ts"],"names":[],"mappings":";;AAAA,uCAAsD;AAEtD,wBAAa,CAAC,iBAAiB,CAAC,cAAc,EAAE;IAC9C,eAAe,EAAE,GAAG,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,iBAAiB,CAAC;IAC5D,iBAAiB,EAAE,GAAG,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,mBAAmB,CAAC;IAChE,OAAO,EAAE,GAAG,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,SAAS,CAAC;IAC5C,eAAe,EAAE,GAAG,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,iBAAiB,CAAC;IAE5D,WAAW,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,oBAAoB,EAAE,IAAI,CAAC;IAC7E,OAAO,EAAE,CAAC,IAAS,EAAE,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,gBAAgB,EAAE,IAAI,CAAC;IAClE,YAAY,EAAE,CAAC,IAAS,EAAE,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,qBAAqB,EAAE,IAAI,CAAC;IAC5E,aAAa,EAAE,CAAC,MAAc,EAAE,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,sBAAsB,EAAE,MAAM,CAAC;IACrF,cAAc,EAAE,CAAC,IAAS,EAAE,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,uBAAuB,EAAE,IAAI,CAAC;IAChF,kBAAkB,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,2BAA2B,EAAE,IAAI,CAAC;IAE3F,WAAW,EAAE,CAAC,IAAS,EAAE,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,oBAAoB,EAAE,IAAI,CAAC;IAC1E,iBAAiB,EAAE,CAAC,IAAS,EAAE,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,0BAA0B,EAAE,IAAI,CAAC;IACtF,iBAAiB,EAAE,CAAC,IAAS,EAAE,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,0BAA0B,EAAE,IAAI,CAAC;IACtF,mBAAmB,EAAE,CAAC,IAAS,EAAE,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,4BAA4B,EAAE,IAAI,CAAC;IAC1F,oBAAoB,EAAE,CAAC,IAAS,EAAE,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,6BAA6B,EAAE,IAAI,CAAC;IAC5F,UAAU,EAAE,GAAG,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,mBAAmB,CAAC;IAEzD,uBAAuB;IACvB,aAAa,EAAE,CAAC,IAAS,EAAE,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,sBAAsB,EAAE,IAAI,CAAC;IAC9E,UAAU,EAAE,CAAC,IAAS,EAAE,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,mBAAmB,EAAE,IAAI,CAAC;IACxE,cAAc,EAAE,CAAC,IAAS,EAAE,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,uBAAuB,EAAE,IAAI,CAAC;IAChF,gBAAgB,EAAE,CAAC,IAAS,EAAE,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,yBAAyB,EAAE,IAAI,CAAC;IACpF,kBAAkB,EAAE,CAAC,IAAS,EAAE,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,2BAA2B,EAAE,IAAI,CAAC;IACxF,aAAa,EAAE,CAAC,IAAS,EAAE,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,sBAAsB,EAAE,IAAI,CAAC;IAC9E,aAAa,EAAE,CAAC,IAAS,EAAE,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,sBAAsB,EAAE,IAAI,CAAC;CAC/E,CAAC,CAAC"}
|
||||
3
dist/shared/types.js
vendored
Normal file
3
dist/shared/types.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
//# sourceMappingURL=types.js.map
|
||||
1
dist/shared/types.js.map
vendored
Normal file
1
dist/shared/types.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/shared/types.ts"],"names":[],"mappings":""}
|
||||
270
index.js
270
index.js
@@ -1,270 +0,0 @@
|
||||
const { app, BrowserWindow, ipcMain, shell, systemPreferences } = require('electron');
|
||||
|
||||
|
||||
const axios = require('axios');
|
||||
const fs = require('fs');
|
||||
//const http = require('http');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const process = require('process');
|
||||
//const request = require('request');
|
||||
//const url = require('url');
|
||||
// const usb = require('usb') // Compiled with an old version of Node.js
|
||||
|
||||
console.log(os.type());
|
||||
console.log(process.getSystemVersion());
|
||||
|
||||
|
||||
if (os.type == 'Darwin') {
|
||||
if (systemPreferences.getMediaAccessStatus('microphone') != 'granted') {
|
||||
systemPreferences.askForMediaAccess('microphone');
|
||||
} else {
|
||||
console.log(systemPreferences.getMediaAccessStatus('microphone'));
|
||||
}
|
||||
|
||||
if (systemPreferences.getMediaAccessStatus('camera') != 'granted') {
|
||||
systemPreferences.askForMediaAccess('camera');
|
||||
} else {
|
||||
console.log(systemPreferences.getMediaAccessStatus('camera'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function createWindow () {
|
||||
// Create the browser window.
|
||||
win = new BrowserWindow({
|
||||
width: 1500, // 1500 1280
|
||||
height: 1024, // 1024
|
||||
backgroundColor: '#aaa',
|
||||
icon: './app/img/favicon.ico',
|
||||
webPreferences: {
|
||||
contextIsolation: false,
|
||||
nodeIntegration: true,
|
||||
nodeIntegrationInWorker: true
|
||||
}
|
||||
})
|
||||
|
||||
// win.setMinimumSize(1024, 768);
|
||||
win.setMinimumSize(1280, 768);
|
||||
|
||||
//win.setFullScreenable(false)
|
||||
win.FullScreenable = false;
|
||||
|
||||
// win.webContents.session.clearStorageData(['filesystem']); // Does this do anything???
|
||||
|
||||
// and load the index.html of the app.
|
||||
win.loadFile('app/index.html');
|
||||
|
||||
// Open the DevTools.
|
||||
win.webContents.openDevTools();
|
||||
|
||||
// Emitted when the window is closed.
|
||||
win.on('closed', () => {
|
||||
// Dereference the window object, usually you would store windows
|
||||
// in an array if your app supports multi windows, this is the time
|
||||
// when you should delete the corresponding element.
|
||||
win = null;
|
||||
})
|
||||
|
||||
win.on('minimize', () => {
|
||||
//win.restore();
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// This method will be called when Electron has finished
|
||||
// initialization and is ready to create browser windows.
|
||||
// Some APIs can only be used after this event occurs.
|
||||
app.on('ready', createWindow);
|
||||
|
||||
|
||||
// Quit when all windows are closed.
|
||||
app.on('window-all-closed', () => {
|
||||
// On macOS it is common for applications and their menu bar
|
||||
// to stay active until the user quits explicitly with Cmd + Q
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit();
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
app.on('activate', () => {
|
||||
// On macOS it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.location_files
|
||||
if (win === null) {
|
||||
createWindow();
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
// Download file to path
|
||||
// full_save_path should be the full path that includes the filename
|
||||
// Updated 2022-03-09
|
||||
ipcMain.handle('download_file', async (event, api_base_url, api_endpoint, full_save_path) => {
|
||||
console.log('*** Electron IPC Main: download_file() ***');
|
||||
// console.log('ipcMain on download_file: api_base_url='+api_base_url+' | api_temporary_token='+api_temporary_token);
|
||||
console.log('ipcMain on download_file: api_base_url='+api_base_url);
|
||||
console.log('ipcMain download and save file: HTTP '+api_endpoint+' -> FILE '+full_save_path);
|
||||
|
||||
let result = await download_file(api_base_url, api_endpoint, full_save_path);
|
||||
|
||||
console.log(result);
|
||||
console.log('End: Electron IPC Main: download_file()');
|
||||
// return 'Return from Electron IPC Main download_file()';
|
||||
return result;
|
||||
});
|
||||
|
||||
|
||||
// Download file to path
|
||||
// full_save_path should be the full path that includes the filename
|
||||
// Updated 2022-03-09
|
||||
async function download_file(api_base_url, api_endpoint, full_save_path) {
|
||||
console.log('*** node.js: download_file() ***');
|
||||
|
||||
axios.defaults.baseURL = api_base_url;
|
||||
axios.defaults.headers.common['Access-Control-Allow-Origin'] = '*'; // app_config.access_control_allow_origin;
|
||||
axios.defaults.headers.common['content-type'] = 'application/json';
|
||||
axios.defaults.headers.common['x-aether-api-key'] = 'dFP6J9DVj9hUgIMn-fNIqg'; // api_secret_key;
|
||||
axios.defaults.headers.common['x-account-id'] = '_XY7DXtc9MY'; // account_id;
|
||||
|
||||
const url = api_endpoint;
|
||||
|
||||
let download_result = await axios({
|
||||
method: 'get',
|
||||
url: url,
|
||||
responseType: 'stream' /* responseType must be stream */
|
||||
}).then(function (response) {
|
||||
console.log('Downloading...');
|
||||
// response.data.pipe(fs.createWriteStream(full_save_path));
|
||||
// return true;
|
||||
|
||||
// response.data.pipe(fs.createWriteStream(full_save_path));
|
||||
// return true;
|
||||
|
||||
const writer = fs.createWriteStream(full_save_path);
|
||||
|
||||
console.log('Write stream created');
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
response.data.pipe(writer);
|
||||
let error = null;
|
||||
writer.on('error', err => {
|
||||
console.log('Writer error!');
|
||||
error = err;
|
||||
console.log(error);
|
||||
writer.close();
|
||||
reject(err);
|
||||
});
|
||||
writer.on('close', () => {
|
||||
console.log('Writer close!');
|
||||
if (!error) {
|
||||
resolve(true);
|
||||
}
|
||||
//no need to call the reject here, as it will have been called in the
|
||||
//'error' stream;
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log(`Error downloading! Endpoint: ${api_endpoint}`);
|
||||
// console.log(error);
|
||||
// console.log(error.response);
|
||||
if (error.response && error.response.status === 404) {
|
||||
return null; // Returning null since there were no results
|
||||
}
|
||||
console.log(`Response Status: ${error.response.status}; Status Text: ${error.response.statusText}`);
|
||||
return false; // Returning false since something may have gone wrong. Also more in line with what the API returns.
|
||||
});
|
||||
|
||||
console.log(download_result);
|
||||
console.log('End: download_file()');
|
||||
// return 'Return from download_file()';
|
||||
return download_result;
|
||||
}
|
||||
|
||||
|
||||
ipcMain.handle('open_hash_file_to_temp', async (event, host_file_cache_path, hash, host_file_temp_path, filename) => {
|
||||
console.log('*** Electron IPC Main: open_hash_file_to_temp() ***');
|
||||
console.log('ipcMain on open_hash_file_to_temp');
|
||||
console.log(`ipcMain open hash file from temp directory: ${host_file_cache_path} -> ${host_file_temp_path}/${filename}`);
|
||||
|
||||
let cache_file_path = path.join(process.cwd(), host_file_cache_path);
|
||||
console.log(cache_file_path);
|
||||
|
||||
let hash_filename = hash+'.file';
|
||||
let full_cache_file_path = path.join(cache_file_path, hash_filename);
|
||||
console.log(full_cache_file_path);
|
||||
|
||||
open_temp_file_path = path.join(process.cwd(), host_file_temp_path, filename); // 'temp/'
|
||||
console.log(open_temp_file_path);
|
||||
|
||||
if (fs.existsSync(open_temp_file_path)) {
|
||||
console.log('A file with the same name already exists in the local temp directory: '+open_temp_file_path);
|
||||
// NOTE: Should the file be checked to see if it has changed from the hashed cache version???
|
||||
// NOTE: What if they made changes to the file locally in temp? The changed file would be used since a new copy is not being made.
|
||||
// NOTE: It might make sense for this to be a configurable option depending on the group. Some do not allow changes. This helps enforce that.
|
||||
}
|
||||
|
||||
if (fs.existsSync(full_cache_file_path)) {
|
||||
console.log(`Hashed file exists in cache: ${full_cache_file_path}`);
|
||||
console.log(`Copying file to temp: ${open_temp_file_path}`);
|
||||
try {
|
||||
await fs.copyFileSync(full_cache_file_path, open_temp_file_path);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
// console.log('Creating file link: '+open_temp_file_path);
|
||||
// fs.linkSync(full_cache_file_path, open_temp_file_path);
|
||||
} else {
|
||||
console.log(`Hashed file not found in cache: ${full_cache_file_path}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await shell.openPath(open_temp_file_path);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log(true);
|
||||
console.log('End: Electron IPC Main: open_hash_file_to_temp()');
|
||||
// return 'Return from Electron IPC Main open_hash_file_to_temp()';
|
||||
return true;
|
||||
});
|
||||
|
||||
|
||||
ipcMain.handle('open_local_file', async (event, local_file_path, filename, use_cwd=true) => {
|
||||
console.log('*** Electron IPC Main: open_local_file() ***');
|
||||
console.log('ipcMain on open_local_file');
|
||||
console.log(`ipcMain open local file from directory: ${local_file_path}/${filename}`);
|
||||
|
||||
let full_local_file_path = null;
|
||||
|
||||
if (use_cwd) {
|
||||
full_local_file_path = path.join(process.cwd(), local_file_path, filename);
|
||||
console.log(full_local_file_path);
|
||||
} else {
|
||||
full_local_file_path = path.join(local_file_path, filename);
|
||||
console.log(full_local_file_path);
|
||||
}
|
||||
|
||||
if (fs.existsSync(full_local_file_path)) {
|
||||
console.log(`Local file exists: ${full_local_file_path}`);
|
||||
} else {
|
||||
console.log(`Local file not found: ${full_local_file_path}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await shell.openPath(full_local_file_path);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log('End: Electron IPC Main: open_local_file()');
|
||||
return true;
|
||||
});
|
||||
3776
package-lock.json
generated
3776
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
34
package.json
34
package.json
@@ -1,23 +1,23 @@
|
||||
{
|
||||
"name": "onesky_native",
|
||||
"version": "2.0.0",
|
||||
"description": "One Sky Native App",
|
||||
"main": "index.js",
|
||||
"name": "aether_app_native_electron",
|
||||
"version": "1.0.0",
|
||||
"description": "AE Native Launcher V3",
|
||||
"main": "dist/main/index.js",
|
||||
"scripts": {
|
||||
"start": "electron .",
|
||||
"start_nogpu": "electron . --disable-gpu",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"start": "tsc && electron .",
|
||||
"dev": "tsc && electron .",
|
||||
"build": "tsc",
|
||||
"watch": "tsc -w",
|
||||
"package:mac": "tsc && electron-packager . aether_launcher --platform=darwin --arch=x64,arm64 --out=builds --overwrite --prune=true --icon=resources/img/osit_logo.icns"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.10.7",
|
||||
"electron": "^34.0.0",
|
||||
"electron-packager": "^17.1.2",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.7.3"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"axios": "^0.26.0",
|
||||
"electron": "^17.1.0",
|
||||
"fs": "0.0.1-security",
|
||||
"os": "^0.1.1",
|
||||
"path": "^0.12.7",
|
||||
"request": "^2.88.2",
|
||||
"screen": "^0.2.10",
|
||||
"usb": "^1.6.2"
|
||||
"axios": "^1.13.2"
|
||||
}
|
||||
}
|
||||
|
||||
79
src/main/api_client.ts
Normal file
79
src/main/api_client.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { SeedConfig } from '../shared/types';
|
||||
|
||||
export async function fetchFullConfig(seed: SeedConfig): Promise<any> {
|
||||
const apiUrls = [
|
||||
seed.onsite_api_base_url,
|
||||
seed.primary_api_base_url,
|
||||
seed.backup_api_base_url
|
||||
].filter(url => url !== null && url !== undefined) as string[];
|
||||
|
||||
let lastError: any = null;
|
||||
|
||||
for (const baseUrl of apiUrls) {
|
||||
try {
|
||||
console.log(`Bootstrap: Attempting connection to ${baseUrl}...`);
|
||||
|
||||
// --- STEP 1: Get Device Config ---
|
||||
const deviceUrl = `${baseUrl}/v3/crud/event_device/${seed.event_device_id}`;
|
||||
const deviceResponse = await fetch(deviceUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-aether-api-key': seed.aether_api_key,
|
||||
'x-no-account-id': 'Nothing to See Here'
|
||||
},
|
||||
});
|
||||
|
||||
if (!deviceResponse.ok) {
|
||||
throw new Error(`Device lookup failed (${deviceResponse.status})`);
|
||||
}
|
||||
|
||||
const deviceResult = await deviceResponse.json();
|
||||
const deviceData = deviceResult.data || deviceResult;
|
||||
|
||||
// Use 'app_base_url' as the FQDN for the site lookup
|
||||
const fqdn = deviceData.app_base_url || 'native-demo.oneskyit.com';
|
||||
|
||||
// --- STEP 2: Get Site Context ---
|
||||
const searchUrl = `${baseUrl}/v3/crud/site_domain/search`;
|
||||
const siteResponse = await fetch(searchUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-aether-api-key': seed.aether_api_key,
|
||||
'x-no-account-id': 'Nothing to See Here',
|
||||
'x-account-id': deviceData.account_id_random || deviceData.account_id || ''
|
||||
},
|
||||
body: JSON.stringify({
|
||||
search_query: {
|
||||
and: [{ field: 'fqdn', op: 'eq', value: fqdn }]
|
||||
},
|
||||
limit: 1
|
||||
})
|
||||
});
|
||||
|
||||
if (!siteResponse.ok) {
|
||||
throw new Error(`Site context lookup failed (${siteResponse.status})`);
|
||||
}
|
||||
|
||||
const siteResult = await siteResponse.json();
|
||||
const siteDomain = (siteResult.data && siteResult.data.length > 0) ? siteResult.data[0] : null;
|
||||
|
||||
console.log(`Bootstrap Success using ${baseUrl}`);
|
||||
|
||||
return {
|
||||
...siteDomain,
|
||||
native_device: deviceData,
|
||||
aether_api_key: seed.aether_api_key // Include the key for frontend use
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.warn(`Bootstrap failed for ${baseUrl}: `, error);
|
||||
lastError = error;
|
||||
continue; // Try next URL
|
||||
}
|
||||
}
|
||||
|
||||
console.error('Bootstrap Critical Failure: All API endpoints exhausted.', lastError);
|
||||
return null;
|
||||
}
|
||||
30
src/main/config_loader.ts
Normal file
30
src/main/config_loader.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import { SeedConfig } from '../shared/types';
|
||||
|
||||
export async function loadSeedConfig(): Promise<SeedConfig | null> {
|
||||
// For development, we look in the home directory
|
||||
const configPath = path.join(os.homedir(), 'seed.json');
|
||||
|
||||
try {
|
||||
if (!fs.existsSync(configPath)) {
|
||||
console.log(`Seed config not found at: ${configPath}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = fs.readFileSync(configPath, 'utf-8');
|
||||
const config = JSON.parse(data) as SeedConfig;
|
||||
|
||||
// Basic validation
|
||||
if (!config.event_device_id) {
|
||||
console.error('Invalid seed config: missing event_device_id');
|
||||
return null;
|
||||
}
|
||||
|
||||
return config;
|
||||
} catch (error) {
|
||||
console.error('Error loading seed config:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
184
src/main/file_handlers.ts
Normal file
184
src/main/file_handlers.ts
Normal file
@@ -0,0 +1,184 @@
|
||||
import { ipcMain, shell } from 'electron';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import * as crypto from 'crypto';
|
||||
import { exec } from 'child_process';
|
||||
import axios from 'axios';
|
||||
import { expandPath } from './file_utils';
|
||||
|
||||
let endpoints_in_progress: string[] = [];
|
||||
|
||||
export function registerFileHandlers() {
|
||||
// Flexible organization: [root]/[prefix_len-char-prefix]/[hash].file
|
||||
function get_organized_hashed_path(root: string, hash: string, prefix_len: number = 2) {
|
||||
const expanded_root = expandPath(root);
|
||||
const prefix = hash.substring(0, Math.max(1, Math.min(prefix_len, 8)));
|
||||
const dir = path.join(expanded_root, prefix);
|
||||
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
||||
return path.join(dir, `${hash}.file`);
|
||||
}
|
||||
|
||||
ipcMain.handle('native:check-cache', async (event, { cache_root, hash, hash_prefix_length = 2, verify_hash = false }) => {
|
||||
const full_path = get_organized_hashed_path(cache_root, hash, hash_prefix_length);
|
||||
|
||||
if (!fs.existsSync(full_path)) return false;
|
||||
|
||||
if (verify_hash) {
|
||||
try {
|
||||
const file_buffer = fs.readFileSync(full_path);
|
||||
const actual_hash = crypto.createHash('sha256').update(file_buffer).digest('hex');
|
||||
return actual_hash === hash;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
ipcMain.handle('native:download-to-cache', async (event, { url, cache_root, hash, api_key, account_id, hash_prefix_length = 2 }) => {
|
||||
const full_path = get_organized_hashed_path(cache_root, hash, hash_prefix_length);
|
||||
const tmp_path = `${full_path}.tmp`;
|
||||
|
||||
if (endpoints_in_progress.includes(url)) return { success: true, status: 'in_progress' };
|
||||
|
||||
// 1. If final file exists, skip
|
||||
if (fs.existsSync(full_path)) return { success: true, path: full_path, status: 'exists' };
|
||||
|
||||
// 2. Handle stale .tmp files (Legacy "Trust No One" pattern)
|
||||
if (fs.existsSync(tmp_path)) {
|
||||
const stats = fs.statSync(tmp_path);
|
||||
const age_ms = Date.now() - stats.mtimeMs;
|
||||
// If the tmp file is older than 5 minutes, assume previous download crashed and delete it
|
||||
if (age_ms > 5 * 60 * 1000) {
|
||||
console.log(`Native: Deleting stale temp file (${Math.round(age_ms/1000)}s old)`);
|
||||
fs.unlinkSync(tmp_path);
|
||||
} else {
|
||||
return { success: true, status: 'in_progress', detail: 'fresh_tmp_exists' };
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Native: Hardened Download -> ${full_path}`);
|
||||
|
||||
try {
|
||||
endpoints_in_progress.push(url);
|
||||
const response = await axios({
|
||||
method: 'get', url, responseType: 'stream',
|
||||
headers: {
|
||||
'x-aether-api-key': api_key,
|
||||
'x-account-id': account_id || '',
|
||||
'x-no-account-id': 'Nothing to See Here'
|
||||
}
|
||||
});
|
||||
|
||||
const writer = fs.createWriteStream(tmp_path);
|
||||
response.data.pipe(writer);
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
writer.on('finish', () => resolve());
|
||||
writer.on('error', reject);
|
||||
});
|
||||
|
||||
// 3. Verify Integrity before renaming (The "Trust No One" Check)
|
||||
const file_buffer = fs.readFileSync(tmp_path);
|
||||
const actual_hash = crypto.createHash('sha256').update(file_buffer).digest('hex');
|
||||
|
||||
if (actual_hash !== hash) {
|
||||
console.error(`Native: Hash Mismatch! Expected ${hash}, got ${actual_hash}`);
|
||||
fs.unlinkSync(tmp_path);
|
||||
return { success: false, error: 'Integrity check failed: Hash mismatch' };
|
||||
}
|
||||
|
||||
fs.renameSync(tmp_path, full_path);
|
||||
console.log(`Native: Cache Integrity Verified. File moved to final destination.`);
|
||||
return { success: true, path: full_path };
|
||||
} catch (error: any) {
|
||||
if (fs.existsSync(tmp_path)) fs.unlinkSync(tmp_path);
|
||||
return { success: false, error: error.message };
|
||||
} finally {
|
||||
endpoints_in_progress = endpoints_in_progress.filter(e => e !== url);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('native:launch-from-cache', async (event, { cache_root, hash, temp_root, filename, hash_prefix_length = 2 }) => {
|
||||
try {
|
||||
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') {
|
||||
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 1
|
||||
run slide show of active presentation
|
||||
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 };
|
||||
} catch (error: any) {
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
}
|
||||
16
src/main/file_utils.ts
Normal file
16
src/main/file_utils.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
|
||||
export function expandPath(filePath: string): string {
|
||||
if (!filePath) 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 {
|
||||
const expandedRoot = expandPath(cacheRoot);
|
||||
const subdirectory = hash.substring(0, 2);
|
||||
return path.join(expandedRoot, subdirectory, `${hash}.file`);
|
||||
}
|
||||
94
src/main/index.ts
Normal file
94
src/main/index.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { app, BrowserWindow, ipcMain } from 'electron';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import { loadSeedConfig } from './config_loader';
|
||||
import { fetchFullConfig } from './api_client';
|
||||
import { registerShellHandlers } from './shell_handlers';
|
||||
import { registerFileHandlers } from './file_handlers';
|
||||
import { registerSystemHandlers } from './system_handlers';
|
||||
import { SeedConfig } from '../shared/types';
|
||||
|
||||
let mainWindow: BrowserWindow | null = null;
|
||||
let cachedSeed: SeedConfig | null = null;
|
||||
let cachedFullConfig: any = null;
|
||||
|
||||
async function createWindow() {
|
||||
cachedSeed = await loadSeedConfig();
|
||||
if (cachedSeed) {
|
||||
cachedFullConfig = await fetchFullConfig(cachedSeed);
|
||||
}
|
||||
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1600,
|
||||
height: 900,
|
||||
title: 'OSIT Aether Launcher (Native)',
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, '../preload/index.js'),
|
||||
contextIsolation: true,
|
||||
nodeIntegration: false,
|
||||
},
|
||||
});
|
||||
|
||||
let targetUrl = 'http://demo.localhost:5173';
|
||||
if (cachedFullConfig && cachedFullConfig.native_device) {
|
||||
const device = cachedFullConfig.native_device;
|
||||
const eventId = device.event_id_random || device.event_id;
|
||||
const locationId = device.event_location_id_random || device.event_location_id || '';
|
||||
// Use app_base_url from the device record (e.g. bgh.oneskyit.com).
|
||||
// Fall back to localhost only if nothing is configured — never override a real domain.
|
||||
const host = device.app_base_url || 'demo.localhost:5173';
|
||||
// Use https for real domains; localhost dev URLs stay on http
|
||||
const protocol = host.includes('localhost') ? 'http' : 'https';
|
||||
targetUrl = `${protocol}://${host}/events/${eventId}/launcher/${locationId}`;
|
||||
}
|
||||
|
||||
// Only open DevTools in development (not in a packaged .app build)
|
||||
if (!app.isPackaged) mainWindow.webContents.openDevTools();
|
||||
mainWindow.loadURL(targetUrl).catch(() => {
|
||||
mainWindow?.loadURL('https://dev-demo.oneskyit.com/');
|
||||
});
|
||||
|
||||
mainWindow.on('closed', () => { mainWindow = null; });
|
||||
}
|
||||
|
||||
registerShellHandlers();
|
||||
registerFileHandlers();
|
||||
registerSystemHandlers();
|
||||
|
||||
app.on('ready', createWindow);
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') app.quit();
|
||||
});
|
||||
|
||||
app.on('activate', () => {
|
||||
if (mainWindow === null) createWindow();
|
||||
});
|
||||
|
||||
ipcMain.handle('get-seed-config', async () => cachedSeed || await loadSeedConfig());
|
||||
ipcMain.handle('get-device-config', async () => cachedFullConfig);
|
||||
ipcMain.handle('get-jwt', async () => null);
|
||||
|
||||
ipcMain.handle('get-device-info', async () => {
|
||||
const interfaces = os.networkInterfaces();
|
||||
const addresses: string[] = [];
|
||||
for (const name of Object.keys(interfaces)) {
|
||||
for (const net of interfaces[name]!) {
|
||||
if (net.family === 'IPv4' && !net.internal) {
|
||||
addresses.push(net.address);
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
platform: os.platform(),
|
||||
release: os.release(),
|
||||
arch: os.arch(),
|
||||
hostname: os.hostname(),
|
||||
cpus: os.cpus().length,
|
||||
total_mem: os.totalmem(),
|
||||
free_mem: os.freemem(),
|
||||
ip_addresses: addresses,
|
||||
home_directory: os.homedir(),
|
||||
tmp_directory: os.tmpdir()
|
||||
};
|
||||
});
|
||||
211
src/main/shell_handlers.ts
Normal file
211
src/main/shell_handlers.ts
Normal file
@@ -0,0 +1,211 @@
|
||||
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 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(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(cleanCmd).toString();
|
||||
return { success: true, stdout: stdout.trim() };
|
||||
} catch (error: any) {
|
||||
return { success: false, error: error.message, stderr: error.stderr?.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('native:run-osascript', async (event, script: string) => {
|
||||
if (os.platform() !== 'darwin') return { success: false, error: 'AppleScript is only available on macOS' };
|
||||
const escapedScript = script.replace(/"/g, '\"');
|
||||
const cmd = `osascript -e "${escapedScript}"`;
|
||||
return new Promise((resolve) => {
|
||||
exec(cmd, (error, stdout, stderr) => {
|
||||
resolve({ success: !error, stdout: stdout.trim(), stderr: stderr.trim(), error: error ? error.message : null });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.handle('native:kill-processes', async (event, { process_name_li = [] }) => {
|
||||
console.log(`Native: Killing processes -> `, process_name_li);
|
||||
const results = [];
|
||||
for (const name of process_name_li) {
|
||||
const cmd = os.platform() === 'win32'
|
||||
? `taskkill /F /IM ${name} /T`
|
||||
: `pkill -f ${name}`;
|
||||
try {
|
||||
execSync(cmd);
|
||||
results.push({ name, success: true });
|
||||
} catch (e: any) {
|
||||
results.push({ name, success: false, error: e.message });
|
||||
}
|
||||
}
|
||||
return { success: true, results };
|
||||
});
|
||||
|
||||
ipcMain.handle('native:open-local-file-v2', async (event, filePath: string) => {
|
||||
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') {
|
||||
let script = '';
|
||||
if (appType === 'keynote') {
|
||||
script = `
|
||||
tell application "Keynote"
|
||||
activate
|
||||
open (POSIX file "${cleanedPath}")
|
||||
delay 1
|
||||
start (front document)
|
||||
end tell
|
||||
`;
|
||||
} else if (appType === 'powerpoint') {
|
||||
script = `
|
||||
tell application "Microsoft PowerPoint"
|
||||
activate
|
||||
open (POSIX file "${cleanedPath}")
|
||||
delay 1
|
||||
run slide show of active presentation
|
||||
end tell
|
||||
`;
|
||||
}
|
||||
|
||||
if (script) {
|
||||
return new Promise((resolve) => {
|
||||
const escapedScript = script.replace(/"/g, '\\"');
|
||||
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 };
|
||||
});
|
||||
|
||||
ipcMain.handle('native:control-presentation', async (event, { app, action }) => {
|
||||
if (os.platform() !== 'darwin') return { success: false, error: 'Presentation control is only available on macOS' };
|
||||
|
||||
let script = '';
|
||||
if (app === 'powerpoint') {
|
||||
switch (action) {
|
||||
case 'next': script = 'tell application "Microsoft PowerPoint" to next slide of slide show view of active presentation'; break;
|
||||
case 'prev': script = 'tell application "Microsoft PowerPoint" to previous slide of slide show view of active presentation'; break;
|
||||
case 'start': script = 'tell application "Microsoft PowerPoint" to run slide show of active presentation'; break;
|
||||
case 'stop': script = 'tell application "Microsoft PowerPoint" to stop slide show of active presentation'; break;
|
||||
}
|
||||
} else if (app === 'keynote') {
|
||||
switch (action) {
|
||||
case 'next': script = 'tell application "Keynote" to show next'; break;
|
||||
case 'prev': script = 'tell application "Keynote" to show previous'; break;
|
||||
case 'start': script = 'tell application "Keynote" to start (front document)'; break;
|
||||
case 'stop': script = 'tell application "Keynote" to stop'; break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!script) return { success: false, error: `Unsupported app or action: ${app}/${action}` };
|
||||
|
||||
return new Promise((resolve) => {
|
||||
exec(`osascript -e "${script.replace(/"/g, '\\"')}"`, (error, stdout, stderr) => {
|
||||
resolve({ success: !error, stdout: stdout.trim(), stderr: stderr.trim(), error: error ? error.message : null });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.handle('native:list-tools', async () => {
|
||||
return [
|
||||
{
|
||||
name: 'open_folder',
|
||||
description: 'Opens a directory in the OS file explorer (Finder/Files/Explorer).',
|
||||
params: { path: 'string' }
|
||||
},
|
||||
{
|
||||
name: 'run_cmd',
|
||||
description: 'Executes an asynchronous shell command with a timeout.',
|
||||
params: { cmd: 'string', timeout: 'number (optional)' }
|
||||
},
|
||||
{
|
||||
name: 'run_cmd_sync',
|
||||
description: 'Executes a synchronous shell command.',
|
||||
params: { cmd: 'string' }
|
||||
},
|
||||
{
|
||||
name: 'run_osascript',
|
||||
description: 'Executes a raw AppleScript string (macOS only).',
|
||||
params: { script: 'string' }
|
||||
},
|
||||
{
|
||||
name: 'kill_processes',
|
||||
description: 'Forcefully terminates processes by name.',
|
||||
params: { process_name_li: 'string[]' }
|
||||
},
|
||||
{
|
||||
name: 'open_local_file_v2',
|
||||
description: 'Opens a local file using the default OS handler.',
|
||||
params: { filePath: 'string' }
|
||||
},
|
||||
{
|
||||
name: 'launch_presentation',
|
||||
description: 'Phase 5: Specialized launcher for PowerPoint, Keynote, and LibreOffice with auto-focus.',
|
||||
params: { path: 'string', app: 'default|powerpoint|keynote' }
|
||||
},
|
||||
{
|
||||
name: 'control_presentation',
|
||||
description: 'Phase 5: Remote navigation for active slideshows.',
|
||||
params: { app: 'powerpoint|keynote', action: 'next|prev|start|stop' }
|
||||
},
|
||||
{
|
||||
name: 'check_cache',
|
||||
description: 'Checks if a file exists in the local organized cache.',
|
||||
params: { cache_root: 'string', hash: 'string', hash_prefix_length: 'number' }
|
||||
},
|
||||
{
|
||||
name: 'download_to_cache',
|
||||
description: 'Downloads a file from the API directly into the native cache.',
|
||||
params: { url: 'string', cache_root: 'string', hash: 'string', api_key: 'string', account_id: 'string' }
|
||||
},
|
||||
{
|
||||
name: 'launch_from_cache',
|
||||
description: 'Atomic operation: Copies file from cache to temp with original name and launches via specialized handler.',
|
||||
params: { cache_root: 'string', hash: 'string', temp_root: 'string', filename: 'string' }
|
||||
},
|
||||
{
|
||||
name: 'get_device_info',
|
||||
description: 'Returns hardware and OS metadata (CPUs, RAM, IP addresses, Hostname).',
|
||||
params: {}
|
||||
}
|
||||
];
|
||||
});
|
||||
}
|
||||
246
src/main/system_handlers.ts
Normal file
246
src/main/system_handlers.ts
Normal file
@@ -0,0 +1,246 @@
|
||||
import { ipcMain, BrowserWindow, app, shell } from 'electron';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { exec, spawn } from 'child_process';
|
||||
import axios from 'axios';
|
||||
import { expandPath } from './file_utils';
|
||||
|
||||
// Helper to execute shell commands
|
||||
const runExec = (cmd: string): Promise<{ success: boolean; stdout?: string; stderr?: string; error?: string }> => {
|
||||
return new Promise((resolve) => {
|
||||
exec(cmd, (error, stdout, stderr) => {
|
||||
resolve({
|
||||
success: !error,
|
||||
stdout: stdout.trim(),
|
||||
stderr: stderr.trim(),
|
||||
error: error ? error.message : undefined
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
let recordingProcess: any = null;
|
||||
|
||||
export function registerSystemHandlers() {
|
||||
|
||||
// 1. Window Control
|
||||
ipcMain.handle('native:window-control', async (event, { action, value }) => {
|
||||
const win = BrowserWindow.fromWebContents(event.sender);
|
||||
if (!win) return { success: false, error: 'No window found' };
|
||||
|
||||
switch (action) {
|
||||
case 'maximize': win.maximize(); break;
|
||||
case 'unmaximize': win.unmaximize(); break;
|
||||
case 'minimize': win.minimize(); break;
|
||||
case 'restore': win.restore(); break;
|
||||
case 'close': win.close(); break;
|
||||
case 'devtools':
|
||||
if (value) win.webContents.openDevTools();
|
||||
else win.webContents.closeDevTools();
|
||||
break;
|
||||
case 'kiosk': win.setKiosk(!!value); break;
|
||||
case 'fullscreen': win.setFullScreen(!!value); break;
|
||||
case 'reload': win.reload(); break;
|
||||
default: return { success: false, error: `Unknown action: ${action}` };
|
||||
}
|
||||
return { success: true };
|
||||
});
|
||||
|
||||
// 2. Set Wallpaper
|
||||
ipcMain.handle('native:set-wallpaper', async (event, { path: imagePath }) => {
|
||||
const cleanPath = expandPath(imagePath);
|
||||
if (!fs.existsSync(cleanPath)) return { success: false, error: 'Image file not found' };
|
||||
|
||||
if (os.platform() === 'darwin') {
|
||||
const script = `tell application "System Events" to set picture of every desktop to "${cleanPath}"`;
|
||||
return await runExec(`osascript -e '${script}'`);
|
||||
} else if (os.platform() === 'linux') {
|
||||
// Gnome/Ubuntu default
|
||||
return await runExec(`gsettings set org.gnome.desktop.background picture-uri "file://${cleanPath}"`);
|
||||
}
|
||||
|
||||
return { success: false, error: 'Platform not supported' };
|
||||
});
|
||||
|
||||
// 3. Power Control
|
||||
ipcMain.handle('native:power-control', async (event, { action }) => {
|
||||
let cmd = '';
|
||||
const isMac = os.platform() === 'darwin';
|
||||
const isLinux = os.platform() === 'linux';
|
||||
|
||||
if (action === 'shutdown') {
|
||||
if (isMac) cmd = 'shutdown -h now'; // Requires sudo/admin usually
|
||||
if (isLinux) cmd = 'shutdown -h now';
|
||||
} else if (action === 'reboot') {
|
||||
if (isMac) cmd = 'shutdown -r now';
|
||||
if (isLinux) cmd = 'reboot';
|
||||
} else if (action === 'sleep') {
|
||||
if (isMac) cmd = 'pmset sleepnow';
|
||||
if (isLinux) cmd = 'systemctl suspend';
|
||||
}
|
||||
|
||||
if (!cmd) return { success: false, error: 'Action not supported' };
|
||||
|
||||
// NOTE: These commands often require root.
|
||||
// For a kiosk, you might configure sudoers to allow this specific command without password.
|
||||
return await runExec(cmd);
|
||||
});
|
||||
|
||||
// 4. Open External (Browser)
|
||||
ipcMain.handle('native:open-external', async (event, { url, app: appName }) => {
|
||||
if (appName === 'chrome') {
|
||||
if (os.platform() === 'darwin') {
|
||||
return await runExec(`open -a "Google Chrome" "${url}"`);
|
||||
} else if (os.platform() === 'linux') {
|
||||
return await runExec(`google-chrome "${url}"`);
|
||||
}
|
||||
} else if (appName === 'firefox') {
|
||||
if (os.platform() === 'darwin') {
|
||||
return await runExec(`open -a "Firefox" "${url}"`);
|
||||
} else if (os.platform() === 'linux') {
|
||||
return await runExec(`firefox "${url}"`);
|
||||
}
|
||||
}
|
||||
|
||||
// Default system handler
|
||||
await shell.openExternal(url);
|
||||
return { success: true };
|
||||
});
|
||||
|
||||
// 5. Manage Recording (Aperture Wrapper)
|
||||
ipcMain.handle('native:manage-recording', async (event, { action, options }) => {
|
||||
if (os.platform() !== 'darwin') return { success: false, error: 'Recording only supported on macOS' };
|
||||
|
||||
// Path to bundled aperture binary
|
||||
// In dev: ./resources/bin/aperture
|
||||
// In prod: process.resourcesPath/bin/aperture
|
||||
const binPath = app.isPackaged
|
||||
? path.join(process.resourcesPath, 'bin', 'aperture')
|
||||
: path.join(__dirname, '../../resources/bin/aperture'); // Adjust based on structure
|
||||
|
||||
if (action === 'start') {
|
||||
if (recordingProcess) return { success: false, error: 'Recording already in progress' };
|
||||
|
||||
const { fps = 30, audioDeviceId, output } = options || {};
|
||||
const cleanOutput = expandPath(output || '~/tmp/recording.mp4');
|
||||
|
||||
const args = ['run', '--fps', fps, '--output', cleanOutput];
|
||||
if (audioDeviceId) args.push('--audio-device-id', audioDeviceId);
|
||||
|
||||
// Spawn process
|
||||
// Note: aperture is a CLI tool. We might need 'aperture' node package or the binary.
|
||||
// Assuming binary usage here.
|
||||
try {
|
||||
console.log(`Starting recording: ${binPath} ${args.join(' ')}`);
|
||||
recordingProcess = spawn(binPath, args);
|
||||
|
||||
recordingProcess.on('error', (err: any) => {
|
||||
console.error('Recording error:', err);
|
||||
recordingProcess = null;
|
||||
});
|
||||
|
||||
recordingProcess.on('exit', (code: any) => {
|
||||
console.log(`Recording exited with code ${code}`);
|
||||
recordingProcess = null;
|
||||
});
|
||||
|
||||
return { success: true, pid: recordingProcess.pid };
|
||||
} catch (e: any) {
|
||||
return { success: false, error: e.message };
|
||||
}
|
||||
|
||||
} else if (action === 'stop') {
|
||||
if (!recordingProcess) return { success: false, error: 'No recording in progress' };
|
||||
|
||||
recordingProcess.kill('SIGINT'); // Send interrupt to stop cleanly
|
||||
recordingProcess = null;
|
||||
return { success: true };
|
||||
} else if (action === 'status') {
|
||||
return { success: true, isRecording: !!recordingProcess };
|
||||
}
|
||||
|
||||
return { success: false, error: 'Unknown action' };
|
||||
});
|
||||
|
||||
// 6. Set Display Layout (Displayplacer)
|
||||
ipcMain.handle('native:set-display-layout', async (event, { mode, configStr }) => {
|
||||
if (os.platform() !== 'darwin') return { success: false, error: 'Display control only supported on macOS' };
|
||||
|
||||
const binPath = app.isPackaged
|
||||
? path.join(process.resourcesPath, 'bin', 'displayplacer')
|
||||
: path.join(__dirname, '../../resources/bin/displayplacer');
|
||||
|
||||
let cmd = '';
|
||||
|
||||
if (mode === 'mirror') {
|
||||
// This usually requires querying current IDs, which is complex.
|
||||
// If configStr is provided (output of 'displayplacer list'), use it.
|
||||
if (configStr) {
|
||||
cmd = `"${binPath}" ${configStr}`;
|
||||
} else {
|
||||
return { success: false, error: 'Config string required for now' };
|
||||
}
|
||||
} else if (mode === 'extend') {
|
||||
if (configStr) {
|
||||
cmd = `"${binPath}" ${configStr}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (cmd) {
|
||||
return await runExec(cmd);
|
||||
}
|
||||
|
||||
return { success: false, error: 'Invalid mode or missing config' };
|
||||
});
|
||||
|
||||
// 7. Update App
|
||||
ipcMain.handle('native:update-app', async (event, { source, url, path: localPath }) => {
|
||||
// 1. Determine Source File
|
||||
let updateFile = '';
|
||||
const tempDir = os.tmpdir();
|
||||
const destName = 'update_package.zip'; // Or .app, .AppImage
|
||||
const destPath = path.join(tempDir, destName);
|
||||
|
||||
if (source === 'url' && url) {
|
||||
// Download
|
||||
try {
|
||||
const response = await axios({
|
||||
method: 'get',
|
||||
url: url,
|
||||
responseType: 'stream'
|
||||
});
|
||||
|
||||
const writer = fs.createWriteStream(destPath);
|
||||
response.data.pipe(writer);
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
writer.on('finish', () => resolve(true));
|
||||
writer.on('error', reject);
|
||||
});
|
||||
updateFile = destPath;
|
||||
} catch (e: any) {
|
||||
return { success: false, error: `Download failed: ${e.message}` };
|
||||
}
|
||||
} else if (source === 'file' && localPath) {
|
||||
const cleanLocal = expandPath(localPath);
|
||||
if (fs.existsSync(cleanLocal)) {
|
||||
updateFile = cleanLocal;
|
||||
} else {
|
||||
return { success: false, error: 'Local update file not found' };
|
||||
}
|
||||
}
|
||||
|
||||
if (!updateFile) return { success: false, error: 'No update source provided' };
|
||||
|
||||
// 2. Install Logic (Stub)
|
||||
// Real implementation depends on OS and packaging format.
|
||||
// macOS: Mount DMG, copy .app to /Applications? Or Unzip .app?
|
||||
// Linux: chmod +x AppImage and move?
|
||||
|
||||
console.log(`Ready to install update from: ${updateFile}`);
|
||||
|
||||
// For now, just return success so the UI knows we "downloaded" it.
|
||||
return { success: true, message: 'Update downloaded/located. Installation logic requires packaging specifics.', downloadedPath: updateFile };
|
||||
});
|
||||
}
|
||||
31
src/preload/index.ts
Normal file
31
src/preload/index.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { contextBridge, ipcRenderer } from 'electron';
|
||||
|
||||
contextBridge.exposeInMainWorld('aetherNative', {
|
||||
get_seed_config: () => ipcRenderer.invoke('get-seed-config'),
|
||||
get_device_config: () => ipcRenderer.invoke('get-device-config'),
|
||||
get_jwt: () => ipcRenderer.invoke('get-jwt'),
|
||||
get_device_info: () => ipcRenderer.invoke('get-device-info'),
|
||||
|
||||
open_folder: (path: string) => ipcRenderer.invoke('native:open-folder', path),
|
||||
run_cmd: (args: any) => ipcRenderer.invoke('native:run-cmd', args),
|
||||
run_cmd_sync: (args: any) => ipcRenderer.invoke('native:run-cmd-sync', args),
|
||||
run_osascript: (script: string) => ipcRenderer.invoke('native:run-osascript', script),
|
||||
kill_processes: (args: any) => ipcRenderer.invoke('native:kill-processes', args),
|
||||
open_local_file_v2: (path: string) => ipcRenderer.invoke('native:open-local-file-v2', path),
|
||||
|
||||
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),
|
||||
control_presentation: (args: any) => ipcRenderer.invoke('native:control-presentation', args),
|
||||
list_tools: () => ipcRenderer.invoke('native:list-tools'),
|
||||
|
||||
// System Handlers (V5)
|
||||
set_wallpaper: (args: any) => ipcRenderer.invoke('native:set-wallpaper', args),
|
||||
update_app: (args: any) => ipcRenderer.invoke('native:update-app', args),
|
||||
window_control: (args: any) => ipcRenderer.invoke('native:window-control', args),
|
||||
manage_recording: (args: any) => ipcRenderer.invoke('native:manage-recording', args),
|
||||
set_display_layout: (args: any) => ipcRenderer.invoke('native:set-display-layout', args),
|
||||
power_control: (args: any) => ipcRenderer.invoke('native:power-control', args),
|
||||
open_external: (args: any) => ipcRenderer.invoke('native:open-external', args),
|
||||
});
|
||||
40
src/shared/types.ts
Normal file
40
src/shared/types.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
export interface SeedConfig {
|
||||
event_device_id: string;
|
||||
primary_api_base_url: string;
|
||||
backup_api_base_url: string | null;
|
||||
onsite_api_base_url: string | null;
|
||||
aether_api_key: string;
|
||||
}
|
||||
|
||||
export interface AetherNativeBridge {
|
||||
get_seed_config: () => Promise<SeedConfig | null>;
|
||||
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}>;
|
||||
run_cmd_sync: (args: {cmd: string}) => Promise<{success: boolean, stdout: string, error?: string, stderr?: string}>;
|
||||
run_osascript: (script: string) => Promise<{success: boolean, stdout: string, stderr: string, error?: string}>;
|
||||
kill_processes: (args: {process_name_li: string[]}) => Promise<{success: boolean, results: any[]}>;
|
||||
open_local_file_v2: (path: string) => Promise<{success: boolean, error?: string}>;
|
||||
|
||||
// File/Cache Handlers
|
||||
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}>;
|
||||
launch_from_cache: (args: {cache_root: string, hash: string, temp_root: string, filename: string, hash_prefix_length?: number}) => 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}>;
|
||||
control_presentation: (args: {app: 'powerpoint' | 'keynote', action: 'next' | 'prev' | 'start' | 'stop'}) => Promise<{success: boolean, error?: string, stdout?: string, stderr?: string}>;
|
||||
|
||||
// Self-Documentation
|
||||
list_tools: () => Promise<Array<{name: string, description: string, params: object}>>;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
aetherNative: AetherNativeBridge;
|
||||
}
|
||||
}
|
||||
55
tests/test_device_lookup.py
Normal file
55
tests/test_device_lookup.py
Normal file
@@ -0,0 +1,55 @@
|
||||
import requests
|
||||
import json
|
||||
|
||||
def test_device_lookup():
|
||||
device_id = 'dbgMWS3KEHE'
|
||||
api_key = 'INSdG85ANwsEIru3nUttMw'
|
||||
base_url = 'https://dev-api.oneskyit.com'
|
||||
endpoint = f'{base_url}/v3/crud/event_device/{device_id}'
|
||||
|
||||
headers = {
|
||||
'x-aether-api-key': api_key,
|
||||
'x-no-account-id': 'Nothing to See Here',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
params = {
|
||||
'view': 'enriched'
|
||||
}
|
||||
|
||||
print(f'Testing lookup for device: {device_id}')
|
||||
print(f'Endpoint: {endpoint}')
|
||||
|
||||
try:
|
||||
response = requests.get(endpoint, headers=headers, params=params)
|
||||
print(f'Status Code: {response.status_code}')
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
device_data = data.get('data', {})
|
||||
|
||||
print('Returned Fields (Key Values):')
|
||||
important_fields = [
|
||||
'account_id_random',
|
||||
'app_base_url',
|
||||
'code',
|
||||
'name',
|
||||
'event_id_random',
|
||||
'event_location_id_random',
|
||||
'local_file_cache_path',
|
||||
'host_file_temp_path',
|
||||
'recording_path',
|
||||
'cfg_json'
|
||||
]
|
||||
|
||||
for field in important_fields:
|
||||
val = device_data.get(field, 'MISSING')
|
||||
print(f' {field}: {val}')
|
||||
else:
|
||||
print(f'Error Response: {response.text}')
|
||||
|
||||
except Exception as e:
|
||||
print(f'Request failed: {e}')
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_device_lookup()
|
||||
21
tsconfig.json
Normal file
21
tsconfig.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "CommonJS",
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"moduleResolution": "node",
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"sourceMap": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@shared/*": ["src/shared/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
Reference in New Issue
Block a user