Initial scaffold for Aether Native V3 Electron Launcher

This commit is contained in:
Scott Idem
2026-01-23 13:54:20 -05:00
parent fdbd12b64f
commit 0497f5767b
44 changed files with 537 additions and 7294 deletions

79
src/main/api_client.ts Normal file
View 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';
console.log(`Bootstrap Step 1 Success: Device identified. FQDN to use: ${fqdn}`);
// --- 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
};
} 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
View 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;
}
}

76
src/main/index.ts Normal file
View File

@@ -0,0 +1,76 @@
import { app, BrowserWindow, ipcMain } from 'electron';
import * as path from 'path';
import { loadSeedConfig } from './config_loader';
import { fetchFullConfig } from './api_client';
import { SeedConfig } from '../shared/types';
let mainWindow: BrowserWindow | null = null;
let cachedSeed: SeedConfig | null = null;
let cachedFullConfig: any = null;
async function createWindow() {
// 1. Initial Load of Configs
cachedSeed = await loadSeedConfig();
if (cachedSeed) {
cachedFullConfig = await fetchFullConfig(cachedSeed);
}
mainWindow = new BrowserWindow({
width: 1400,
height: 900,
title: 'OSIT Aether Launcher (Native)',
webPreferences: {
preload: path.join(__dirname, '../preload/index.js'),
contextIsolation: true,
nodeIntegration: false,
},
});
// Prioritize demo.localhost for local development
const devUrl = 'http://demo.localhost:5173';
// Fallback URL if local is offline
const fallbackUrl = 'https://dev-demo.oneskyit.com/';
console.log(`Loading UI from: ${devUrl}`);
mainWindow.loadURL(devUrl).catch(() => {
console.warn(`Failed to load ${devUrl}. Falling back to ${fallbackUrl}`);
mainWindow?.loadURL(fallbackUrl);
});
mainWindow.on('closed', () => {
mainWindow = null;
});
}
app.on('ready', createWindow);
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
if (mainWindow === null) {
createWindow();
}
});
// IPC Handlers
ipcMain.handle('get-seed-config', async () => {
return cachedSeed || await loadSeedConfig();
});
ipcMain.handle('get-device-config', async () => {
if (cachedFullConfig) return cachedFullConfig;
if (cachedSeed) {
cachedFullConfig = await fetchFullConfig(cachedSeed);
return cachedFullConfig;
}
return null;
});
ipcMain.handle('get-jwt', async () => {
return null;
});

8
src/preload/index.ts Normal file
View File

@@ -0,0 +1,8 @@
import { contextBridge, ipcRenderer } from 'electron';
contextBridge.exposeInMainWorld('aetherNative', {
getSeedConfig: () => ipcRenderer.invoke('get-seed-config'),
getDeviceConfig: () => ipcRenderer.invoke('get-device-config'),
getJWT: () => ipcRenderer.invoke('get-jwt'),
log: (message: string) => console.log('[Native Log]', message),
});

20
src/shared/types.ts Normal file
View File

@@ -0,0 +1,20 @@
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 {
getSeedConfig: () => Promise<SeedConfig | null>;
getDeviceConfig: () => Promise<any>;
getJWT: () => Promise<string | null>;
log: (message: string) => void;
}
declare global {
interface Window {
aetherNative: AetherNativeBridge;
}
}