171 lines
5.5 KiB
JavaScript
171 lines
5.5 KiB
JavaScript
'use strict';
|
|
|
|
const { app, BrowserWindow, ipcMain, 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;
|
|
|
|
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-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', () => {
|
|
const state = 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() {
|
|
const handler = createBridgeHandler({
|
|
bridgeAuth: pairingController.bridgeAuth,
|
|
sessionStore: runtime.sessionStore,
|
|
onConnectionStateChanged: 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() });
|
|
pairingController = new PairingController({
|
|
envelopePath: path.join(app.getPath('userData'), PAIRING_ENVELOPE_FILENAME),
|
|
});
|
|
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();
|
|
});
|
|
|
|
app.on('before-quit', () => {
|
|
if (bridgeServer) bridgeServer.close();
|
|
if (runtime) {
|
|
for (const proxy of runtime.getConnectionState().activeProxies) runtime.stopProxy(proxy.processId);
|
|
runtime.sessionStore.dispose();
|
|
}
|
|
});
|
|
|
|
app.on('window-all-closed', () => {
|
|
if (process.platform !== 'darwin') app.quit();
|
|
});
|
|
|
|
app.on('activate', () => {
|
|
if (BrowserWindow.getAllWindows().length === 0 && runtime) createWindow();
|
|
});
|