Files
Alta-Proxy-Tool/main.js
peji ace4568f81
APT build checks / build-checks (push) Successful in 37s
APT build checks / build-checks (pull_request) Successful in 37s
feat: release passwordless hierarchical APT v1.2.5
2026-08-22 16:20:12 +00:00

242 lines
8.1 KiB
JavaScript

'use strict';
const { app, BrowserWindow, dialog, ipcMain, safeStorage, shell } = require('electron');
const http = require('node:http');
const path = require('node:path');
const { pathToFileURL } = require('node:url');
const { createSessionStore } = require('./src/session-store');
const { createAltaClient } = require('./src/alta-client');
const { createProxyManager } = require('./src/proxy-launch');
const { checkForUpdate } = require('./src/update-policy');
const {
AppRuntime,
PAIRING_ENVELOPE_FILENAME,
PairingController,
createBridgeHandler,
} = require('./src/electron-runtime');
const BRIDGE_HOST = '127.0.0.1';
const BRIDGE_PORT = 18247;
let mainWindow = null;
let bridgeServer = null;
let runtime = null;
let pairingController = null;
let firstRunPairingSecret = null;
let quitCleanupInProgress = false;
let allowConfirmedQuit = false;
function getAppDirectory() {
return app.isPackaged
? path.dirname(process.env.PORTABLE_EXECUTABLE_FILE || app.getPath('exe'))
: __dirname;
}
function sendConnectionState() {
if (mainWindow && !mainWindow.isDestroyed() && runtime) {
mainWindow.webContents.send('connection-state-changed', runtime.getConnectionState());
}
}
function createWindow() {
mainWindow = new BrowserWindow({
width: 1400,
height: 900,
icon: path.join(__dirname, 'assets', 'icon.png'),
title: 'Alta Video Camera Proxy',
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
sandbox: true,
preload: path.join(__dirname, 'preload.js'),
},
});
mainWindow.webContents.setWindowOpenHandler(() => ({ action: 'deny' }));
mainWindow.webContents.on('will-navigate', (event) => event.preventDefault());
mainWindow.loadFile(path.join(__dirname, 'index.html'));
if (process.argv.includes('--dev')) mainWindow.webContents.openDevTools();
}
function isTrustedSender(event) {
if (!mainWindow || mainWindow.isDestroyed() || !event) return false;
const webContents = mainWindow.webContents;
const frame = event.senderFrame;
const expectedUrl = pathToFileURL(path.join(__dirname, 'index.html')).href;
return event.sender === webContents &&
frame === webContents.mainFrame &&
frame.url === expectedUrl;
}
function registerIpc(channel, handler) {
ipcMain.handle(channel, async (event, ...args) => {
if (!isTrustedSender(event)) throw new Error('Forbidden IPC sender');
return handler(...args);
});
}
function registerIpcHandlers() {
registerIpc('get-devices', () => runtime.getDevices());
registerIpc('get-device-sites', () => runtime.getDeviceSites());
registerIpc('get-device-groups', () => runtime.getDeviceGroups());
registerIpc('get-device-hierarchy', () => runtime.getDeviceHierarchy());
registerIpc('get-auth-info', () => runtime.getAuthInfo());
registerIpc('launch-proxy', (deviceId) => runtime.launchProxy(deviceId));
registerIpc('stop-proxy', async (key) => {
const result = await runtime.stopProxy(key);
sendConnectionState();
return result;
});
registerIpc('disconnect', async () => {
const state = await runtime.disconnect();
sendConnectionState();
return state;
});
registerIpc('get-connection-state', () => runtime.getConnectionState());
registerIpc('check-for-updates', () => runtime.checkForUpdates());
registerIpc('open-fixed-releases-page', () => runtime.openFixedReleasesPage());
registerIpc('rotate-pairing', () => {
firstRunPairingSecret = null;
return pairingController.rotate();
});
registerIpc('revoke-pairing', () => {
firstRunPairingSecret = null;
return pairingController.revoke();
});
registerIpc('get-pairing-status', () => {
const result = pairingController.getStatus();
if (firstRunPairingSecret) {
result.secret = firstRunPairingSecret;
firstRunPairingSecret = null;
}
return result;
});
}
function startBridgeServer() {
if (!pairingController.bridgeAuth) return;
const handler = createBridgeHandler({
bridgeAuth: pairingController.bridgeAuth,
sessionStore: runtime.sessionStore,
onConnectionStateChanged: () => {
runtime.onSessionChanged();
sendConnectionState();
},
});
bridgeServer = http.createServer((request, response) => {
handler(request, response).catch(() => {
if (!response.writableEnded) {
response.writeHead(500, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' });
response.end(JSON.stringify({ success: false, message: 'Bridge request failed' }));
}
});
});
bridgeServer.on('clientError', (_error, socket) => socket.destroy());
bridgeServer.on('error', (error) => {
console.error(`Bridge server unavailable (${error.code || 'UNKNOWN'}).`);
});
bridgeServer.listen(BRIDGE_PORT, BRIDGE_HOST);
}
app.whenReady().then(() => {
const sessionStore = createSessionStore();
const altaClient = createAltaClient({ sessionStore });
const proxyManager = createProxyManager({ appDirectory: getAppDirectory() });
const secureStorageAvailable = safeStorage && safeStorage.isEncryptionAvailable();
pairingController = new PairingController({
envelopePath: path.join(app.getPath('userData'), PAIRING_ENVELOPE_FILENAME),
protect: secureStorageAvailable ? (value) => safeStorage.encryptString(value) : undefined,
unprotect: secureStorageAvailable ? (value) => safeStorage.decryptString(value) : undefined,
});
const pairingState = pairingController.initialize();
firstRunPairingSecret = pairingState.secret || null;
runtime = new AppRuntime({
sessionStore,
altaClient,
proxyManager,
checkForUpdate,
currentVersion: app.getVersion(),
openExternal: (url) => shell.openExternal(url),
});
registerIpcHandlers();
createWindow();
startBridgeServer();
});
function closeBridgeServer() {
if (!bridgeServer) return Promise.resolve();
const server = bridgeServer;
return new Promise((resolve, reject) => {
const finish = (error) => {
if (!error || error.code === 'ERR_SERVER_NOT_RUNNING') {
if (bridgeServer === server) bridgeServer = null;
resolve();
return;
}
reject(error);
};
try { server.close(finish); } catch (error) { finish(error); }
});
}
async function notifyQuitBlocked(state) {
sendConnectionState();
const activeCount = state && Array.isArray(state.activeProxies) ? state.activeProxies.length : 0;
const activeProxiesRemain = activeCount > 0;
const processDetail = activeProxiesRemain
? `${activeCount} app-owned proxy process${activeCount === 1 ? '' : 'es'} remain active.`
: 'No app-owned proxy process remains active.';
const sessionDetail = state && state.connected
? ' The Alta session is still connected; resolve the process error and try quitting again.'
: ' Resolve the shutdown error and try quitting again.';
await dialog.showMessageBox({
type: 'error',
title: activeProxiesRemain ? 'Alta Camera Proxy is still running' : 'Alta Camera Proxy could not close',
message: activeProxiesRemain
? 'The app could not confirm that every proxy process exited.'
: 'The app could not complete shutdown safely.',
detail: `${processDetail}${sessionDetail}`,
buttons: ['OK'],
noLink: true,
});
}
app.on('before-quit', (event) => {
if (allowConfirmedQuit) return;
event.preventDefault();
if (quitCleanupInProgress) return;
quitCleanupInProgress = true;
(async () => {
try {
const state = runtime
? await runtime.disconnect()
: { success: true, connected: false, activeProxies: [] };
if (!state.success || state.activeProxies.length > 0) {
await notifyQuitBlocked(state);
return;
}
await closeBridgeServer();
if (runtime) runtime.sessionStore.dispose();
allowConfirmedQuit = true;
app.quit();
} catch {
const state = runtime
? runtime.getConnectionState()
: { connected: false, activeProxies: [] };
await notifyQuitBlocked(state);
} finally {
quitCleanupInProgress = false;
}
})();
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit();
});
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0 && runtime) createWindow();
});