Files
Alta-Proxy-Tool/renderer.js
T
peji 67437a0c46 Add Chrome extension cookie bridge for session import
Users logged into Alta in Chrome can now send their session cookie
to the running Electron app via a local HTTP server on port 18247,
eliminating the need for re-authentication.

- main.js: HTTP cookie server with CORS, token, domain validation
- preload.js: onExtensionCookie push-pattern IPC bridge
- renderer.js: handleExtensionCookie sets session, fetches devices
- chrome-extension/: Manifest V3 extension with popup UI
- CLAUDE.md: updated architecture docs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 20:58:54 -05:00

1102 lines
40 KiB
JavaScript

// Global variables to store session data
let sessionData = {
deploymentUrl: '',
cookies: null,
isConnected: false
};
// DOM elements
const connectBtn = document.getElementById('connectBtn');
const connectionStatus = document.getElementById('connectionStatus');
const deviceStatus = document.getElementById('deviceStatus');
const deviceList = document.getElementById('deviceList');
const deviceDetails = document.getElementById('deviceDetails');
const statusIndicator = document.getElementById('statusIndicator');
const deviceUUID = document.getElementById('deviceUUID');
const deviceSearch = document.getElementById('deviceSearch');
// New buttons for the layout
const testConnectionBtn = document.getElementById('testConnectionBtn');
const disconnectBtn = document.getElementById('disconnectBtn');
const startProxyBtn = document.getElementById('startProxyBtn');
const checkVersionBtn = document.getElementById('checkVersionBtn');
const stopProxyBtn = document.getElementById('stopProxyBtn');
// Cookie proxy elements
const cookieDeviceUUID = document.getElementById('cookieDeviceUUID');
const cookieKey = document.getElementById('cookieKey');
const startCookieProxyBtn = document.getElementById('startCookieProxyBtn');
const stopCookieProxyBtn = document.getElementById('stopCookieProxyBtn');
// Profile management elements
const profileSelect = document.getElementById('profileSelect');
const addProfileBtn = document.getElementById('addProfileBtn');
const manageProfilesBtn = document.getElementById('manageProfilesBtn');
const addProfileModal = document.getElementById('addProfileModal');
const manageProfilesModal = document.getElementById('manageProfilesModal');
const addProfileForm = document.getElementById('addProfileForm');
const profilesList = document.getElementById('profilesList');
// Track selected device and profiles
let selectedDevice = null;
let currentProfiles = [];
let selectedProfile = null;
let activeProxyConnections = new Map(); // Track multiple active connections
let activeCookieProxyConnections = new Map(); // Track cookie-based proxy connections
const MAX_PROXY_CONNECTIONS = 2; // Limit to 2 simultaneous connections
let allDevices = []; // Store all devices for search functionality
// Event listeners
connectBtn.addEventListener('click', handleConnection);
testConnectionBtn.addEventListener('click', handleTestConnection);
disconnectBtn.addEventListener('click', handleDisconnect);
startProxyBtn.addEventListener('click', handleStartProxy);
checkVersionBtn.addEventListener('click', handleCheckVersion);
stopProxyBtn.addEventListener('click', handleStopProxy);
// Cookie proxy event listeners
startCookieProxyBtn.addEventListener('click', handleStartCookieProxy);
stopCookieProxyBtn.addEventListener('click', handleStopCookieProxy);
// Cookie key input listener to update button states
cookieKey.addEventListener('input', updateCookieProxyButtonStates);
// Device search event listener
deviceSearch.addEventListener('input', handleDeviceSearch);
// Cookie section collapsible header
document.getElementById('cookieSectionHeader').addEventListener('click', toggleCookieSection);
// Profile management event listeners
profileSelect.addEventListener('change', handleProfileSelection);
addProfileBtn.addEventListener('click', showAddProfileModal);
manageProfilesBtn.addEventListener('click', showManageProfilesModal);
addProfileForm.addEventListener('submit', handleAddProfile);
// Modal event listeners
document.getElementById('closeAddProfile').addEventListener('click', hideAddProfileModal);
document.getElementById('cancelAddProfile').addEventListener('click', hideAddProfileModal);
document.getElementById('closeManageProfiles').addEventListener('click', hideManageProfilesModal);
document.getElementById('closeManageProfilesBtn').addEventListener('click', hideManageProfilesModal);
// Close modals when clicking outside
addProfileModal.addEventListener('click', (e) => {
if (e.target === addProfileModal) hideAddProfileModal();
});
manageProfilesModal.addEventListener('click', (e) => {
if (e.target === manageProfilesModal) hideManageProfilesModal();
});
// Handle connection button click
async function handleConnection() {
if (!selectedProfile) {
showStatus(connectionStatus, 'Please select a profile to connect', 'error');
return;
}
// Disable button during connection
setConnectButtonEnabled(false);
showStatus(connectionStatus, 'Connecting to Alta API...', 'info');
try {
const result = await window.electronAPI.login({
deploymentUrl: selectedProfile.deploymentUrl,
username: selectedProfile.username,
password: selectedProfile.password
});
if (result.success) {
// Store session data
sessionData.deploymentUrl = selectedProfile.deploymentUrl;
sessionData.cookies = result.cookies;
sessionData.isConnected = true;
showStatus(connectionStatus, 'Connected successfully!', 'success');
updateConnectionStatus(true);
updateButtonStates();
// Auto-retrieve devices when connected
try {
await handleGetDevices();
} catch (deviceError) {
console.error('Failed to auto-fetch devices:', deviceError);
showStatus(deviceStatus, 'Connected, but failed to load devices. Try selecting a profile and reconnecting.', 'warning');
}
} else {
showStatus(connectionStatus, `Connection failed: ${result.message}`, 'error');
setConnectButtonEnabled(true);
}
} catch (error) {
console.error('Connection error:', error);
showStatus(connectionStatus, `Connection error: ${error.message}`, 'error');
setConnectButtonEnabled(true);
}
}
// Handle test connection
async function handleTestConnection() {
if (!sessionData.isConnected) {
showStatus(connectionStatus, 'Please connect to API first', 'error');
return;
}
showStatus(connectionStatus, 'Testing connection...', 'info');
try {
const result = await window.electronAPI.getAuthInfo({
deploymentUrl: sessionData.deploymentUrl,
cookies: sessionData.cookies
});
if (result.success) {
showStatus(connectionStatus, 'Connection test successful!', 'success');
} else {
showStatus(connectionStatus, `Connection test failed: ${result.message}`, 'error');
}
} catch (error) {
console.error('Test connection error:', error);
showStatus(connectionStatus, `Test failed: ${error.message}`, 'error');
}
}
// Handle disconnect
function handleDisconnect() {
sessionData.isConnected = false;
sessionData.cookies = null;
sessionData.deploymentUrl = '';
selectedDevice = null;
activeProxyConnections.clear();
activeCookieProxyConnections.clear();
allDevices = []; // Clear stored devices
updateConnectionStatus(false);
updateButtonStates();
clearDeviceList();
deviceUUID.value = '';
cookieDeviceUUID.value = '';
cookieKey.value = '';
deviceSearch.value = ''; // Clear search input
// Clear device status message
deviceStatus.style.display = 'none';
deviceStatus.textContent = '';
// Reset connect button text if it was stuck
connectBtn.textContent = 'Connect to API';
connectBtn.disabled = !selectedProfile;
// Reset proxy buttons
startProxyBtn.disabled = true;
stopProxyBtn.disabled = true;
showStatus(connectionStatus, 'Disconnected from API', 'info');
}
// Handle start proxy
async function handleStartProxy() {
if (!selectedDevice) {
showStatus(connectionStatus, 'Please select a device first', 'error');
return;
}
if (!selectedProfile) {
showStatus(connectionStatus, 'Please select a profile first', 'error');
return;
}
// Check connection limit
if (activeProxyConnections.size >= MAX_PROXY_CONNECTIONS) {
showStatus(connectionStatus, `Maximum of ${MAX_PROXY_CONNECTIONS} camera proxy connections allowed. Stop an existing connection first.`, 'warning');
return;
}
// Check if this device already has an active connection
const deviceId = selectedDevice.guid || selectedDevice.id;
if (activeProxyConnections.has(deviceId)) {
showStatus(connectionStatus, `Camera proxy already running for device ${selectedDevice.name}`, 'warning');
return;
}
// Check if camera proxy executable exists
try {
const checkResult = await window.electronAPI.checkCameraProxy();
if (!checkResult.exists) {
showStatus(connectionStatus, 'Camera proxy executable (aware-cam-proxy-win.exe) not found in application directory', 'error');
return;
}
} catch (error) {
showStatus(connectionStatus, 'Failed to check camera proxy executable', 'error');
return;
}
// Disable button during launch
startProxyBtn.disabled = true;
showStatus(connectionStatus, `Starting camera proxy for device ${selectedDevice.name}...`, 'info');
try {
const result = await window.electronAPI.launchCameraProxy({
deploymentUrl: selectedProfile.deploymentUrl,
username: selectedProfile.username,
password: selectedProfile.password,
deviceUuid: deviceId
});
if (result.success) {
// Track this connection
activeProxyConnections.set(deviceId, {
processId: result.processId,
deviceName: selectedDevice.name,
deviceId: deviceId,
startTime: Date.now()
});
updateProxyButtonStates();
showStatus(connectionStatus, `${result.message} (${activeProxyConnections.size}/${MAX_PROXY_CONNECTIONS} connections active)`, 'success');
} else {
showStatus(connectionStatus, `Failed to start camera proxy: ${result.message}`, 'error');
}
} catch (error) {
console.error('Camera proxy launch error:', error);
showStatus(connectionStatus, `Error launching camera proxy: ${error.message}`, 'error');
} finally {
startProxyBtn.disabled = false;
}
}
// Handle check version
async function handleCheckVersion() {
showStatus(connectionStatus, 'Checking camera proxy version...', 'info');
try {
const result = await window.electronAPI.getCameraProxyVersion();
if (result.success) {
showStatus(connectionStatus, `Camera Proxy Version: ${result.version}`, 'info');
} else {
showStatus(connectionStatus, `Failed to get version: ${result.message}`, 'error');
}
} catch (error) {
console.error('Version check error:', error);
showStatus(connectionStatus, `Error checking version: ${error.message}`, 'error');
}
}
// Handle stop proxy
async function handleStopProxy() {
if (activeProxyConnections.size === 0) {
showStatus(connectionStatus, 'No active camera proxy connections found', 'warning');
return;
}
showStatus(connectionStatus, 'Stopping all camera proxy connections...', 'info');
try {
const result = await window.electronAPI.stopCameraProxy(null); // Stop all processes
if (result.success) {
activeProxyConnections.clear();
showStatus(connectionStatus, 'All camera proxy connections stopped successfully', 'success');
// Update visual indicators for all devices
const deviceItems = deviceList.querySelectorAll('.device-item');
deviceItems.forEach(item => {
item.classList.remove('proxy-active');
});
} else {
showStatus(connectionStatus, `Failed to stop camera proxy: ${result.message}`, 'warning');
}
} catch (error) {
console.error('Stop proxy error:', error);
showStatus(connectionStatus, 'Error stopping camera proxy', 'error');
}
updateProxyButtonStates();
updateCookieProxyButtonStates();
}
// Handle start cookie proxy
async function handleStartCookieProxy() {
if (!selectedDevice) {
showStatus(connectionStatus, 'Please select a device first', 'error');
return;
}
const cookieKeyValue = cookieKey.value.trim();
if (!cookieKeyValue) {
showStatus(connectionStatus, 'Please enter a cookie key', 'error');
return;
}
if (!sessionData.deploymentUrl) {
showStatus(connectionStatus, 'Please connect to API first to get deployment URL', 'error');
return;
}
// Check if this device already has an active cookie connection
const deviceId = selectedDevice.guid || selectedDevice.id;
if (activeCookieProxyConnections.has(deviceId)) {
showStatus(connectionStatus, `Cookie proxy already running for device ${selectedDevice.name}`, 'warning');
return;
}
// Disable button during launch
startCookieProxyBtn.disabled = true;
showStatus(connectionStatus, `Starting cookie proxy for device ${selectedDevice.name}...`, 'info');
try {
const result = await window.electronAPI.launchCookieCameraProxy({
deploymentUrl: sessionData.deploymentUrl,
cookieKey: cookieKeyValue,
deviceUuid: deviceId
});
if (result.success) {
// Track this connection
activeCookieProxyConnections.set(deviceId, {
processId: result.processId,
deviceName: selectedDevice.name,
deviceId: deviceId,
startTime: Date.now(),
type: 'cookie'
});
updateCookieProxyButtonStates();
showStatus(connectionStatus, `${result.message} (Cookie proxy active for ${selectedDevice.name})`, 'success');
} else {
showStatus(connectionStatus, `Failed to start cookie proxy: ${result.message}`, 'error');
}
} catch (error) {
console.error('Cookie proxy launch error:', error);
showStatus(connectionStatus, `Error launching cookie proxy: ${error.message}`, 'error');
} finally {
startCookieProxyBtn.disabled = false;
}
}
// Handle stop cookie proxy
async function handleStopCookieProxy() {
if (activeCookieProxyConnections.size === 0) {
showStatus(connectionStatus, 'No active cookie proxy connections found', 'warning');
return;
}
showStatus(connectionStatus, 'Stopping cookie proxy connections...', 'info');
try {
const result = await window.electronAPI.stopCameraProxy(null); // Stop all processes
if (result.success) {
activeCookieProxyConnections.clear();
showStatus(connectionStatus, 'Cookie proxy connections stopped successfully', 'success');
// Update visual indicators for all devices
const deviceItems = deviceList.querySelectorAll('.device-item');
deviceItems.forEach(item => {
item.classList.remove('cookie-proxy-active');
});
} else {
showStatus(connectionStatus, `Failed to stop cookie proxy: ${result.message}`, 'warning');
}
} catch (error) {
console.error('Stop cookie proxy error:', error);
showStatus(connectionStatus, 'Error stopping cookie proxy', 'error');
}
updateCookieProxyButtonStates();
}
// Update cookie proxy button states
function updateCookieProxyButtonStates() {
const hasSelectedDevice = selectedDevice !== null;
const hasCookieKey = cookieKey.value.trim().length > 0;
const hasActiveConnection = activeCookieProxyConnections.size > 0;
if (sessionData.isConnected && hasSelectedDevice && hasCookieKey) {
const deviceId = selectedDevice.guid || selectedDevice.id;
const isThisDeviceActive = activeCookieProxyConnections.has(deviceId);
startCookieProxyBtn.disabled = isThisDeviceActive;
stopCookieProxyBtn.disabled = !hasActiveConnection;
} else {
startCookieProxyBtn.disabled = true;
stopCookieProxyBtn.disabled = !hasActiveConnection;
}
}
// Update connection status indicator
function updateConnectionStatus(connected) {
const statusDot = statusIndicator.querySelector('.status-dot');
const statusText = statusIndicator.querySelector('.status-text');
if (connected) {
statusDot.className = 'status-dot online';
statusText.textContent = 'Connected';
} else {
statusDot.className = 'status-dot offline';
statusText.textContent = 'Disconnected';
}
}
// Update button states based on connection status
function updateButtonStates() {
if (sessionData.isConnected) {
connectBtn.style.display = 'none'; // Hide connect button when connected
testConnectionBtn.disabled = false;
disconnectBtn.disabled = false;
checkVersionBtn.disabled = false;
updateProxyButtonStates(); // Update proxy buttons when connected
} else {
connectBtn.style.display = 'inline-block'; // Show connect button when disconnected
connectBtn.disabled = !selectedProfile;
testConnectionBtn.disabled = true;
disconnectBtn.disabled = true;
startProxyBtn.disabled = true;
checkVersionBtn.disabled = true;
stopProxyBtn.disabled = true;
startCookieProxyBtn.disabled = true;
stopCookieProxyBtn.disabled = true;
}
// Always update cookie proxy button states
updateCookieProxyButtonStates();
}
// Handle get devices (now called automatically)
async function handleGetDevices() {
if (!sessionData.isConnected) {
showStatus(deviceStatus, 'Please connect to the API first', 'error');
return;
}
showStatus(deviceStatus, 'Fetching devices...', 'info');
clearDeviceList();
try {
const result = await window.electronAPI.getDevices({
deploymentUrl: sessionData.deploymentUrl,
cookies: sessionData.cookies
});
if (result.success) {
// Filter devices to only show non-cloud cameras (localStorage = false)
const filteredDevices = result.devices.filter(device => {
// Check if device has capabilities and localStorage property
if (device.capabilities && device.capabilities.localStorage !== undefined) {
// Only show devices where localStorage is false (non-cloud cameras)
return device.capabilities.localStorage === false;
}
// If no capabilities or localStorage property, include the device (fallback)
return true;
});
const totalDevices = result.devices.length;
const filteredCount = filteredDevices.length;
const cloudDevicesHidden = totalDevices - filteredCount;
let statusMessage = `Found ${filteredCount} local camera${filteredCount !== 1 ? 's' : ''}`;
if (cloudDevicesHidden > 0) {
statusMessage += ` (${cloudDevicesHidden} cloud camera${cloudDevicesHidden !== 1 ? 's' : ''} hidden)`;
}
showStatus(deviceStatus, statusMessage, 'success');
// Store all devices for search functionality
allDevices = filteredDevices;
displayDevices(filteredDevices);
} else {
showStatus(deviceStatus, `Failed to get devices: ${result.message}`, 'error');
}
} catch (error) {
console.error('Get devices error:', error);
showStatus(deviceStatus, `Error getting devices: ${error.message}`, 'error');
}
}
// Helper function to determine device status from API data
function getDeviceStatus(device) {
// Check for live.display_status first (Alta API standard)
if (device.live && device.live.display_status) {
const status = device.live.display_status.toLowerCase();
// Handle color-based status responses from Alta API
if (status === 'green') {
return {
isOnline: true,
statusText: 'Online'
};
} else if (status === 'red') {
return {
isOnline: false,
statusText: 'Offline'
};
} else if (status === 'yellow' || status === 'orange') {
return {
isOnline: false,
statusText: 'Warning'
};
}
// Handle text-based status responses
return {
isOnline: status === 'online' || status === 'live' || status === 'connected',
statusText: status === 'online' || status === 'live' || status === 'connected' ? 'Online' : 'Offline'
};
}
// Fallback to other possible status fields
if (device.online !== undefined) {
return {
isOnline: device.online,
statusText: device.online ? 'Online' : 'Offline'
};
}
if (device.status) {
const status = device.status.toLowerCase();
// Handle color-based status in other fields
if (status === 'green') {
return {
isOnline: true,
statusText: 'Online'
};
} else if (status === 'red') {
return {
isOnline: false,
statusText: 'Offline'
};
}
return {
isOnline: status === 'online' || status === 'live' || status === 'connected',
statusText: status === 'online' || status === 'live' || status === 'connected' ? 'Online' : 'Offline'
};
}
// Default to offline if no status information available
return {
isOnline: false,
statusText: 'Offline'
};
}
// Handle device search
function handleDeviceSearch() {
const searchTerm = deviceSearch.value.toLowerCase().trim();
if (!searchTerm) {
// Show all devices if search is empty
displayDevices(allDevices);
return;
}
// Filter devices based on search term
const filteredDevices = allDevices.filter(device => {
const deviceName = (device.name || '').toLowerCase();
const deviceId = (device.guid || device.id || '').toLowerCase();
const deviceType = (device.type || '').toLowerCase();
const deviceModel = (device.model || '').toLowerCase();
const deviceIp = (device.ipAddress || '').toLowerCase();
return deviceName.includes(searchTerm) ||
deviceId.includes(searchTerm) ||
deviceType.includes(searchTerm) ||
deviceModel.includes(searchTerm) ||
deviceIp.includes(searchTerm);
});
displayDevices(filteredDevices);
}
// Display devices in the UI
function displayDevices(devices) {
clearDeviceList();
if (!devices || devices.length === 0) {
const searchTerm = deviceSearch.value.toLowerCase().trim();
const message = searchTerm ? 'No devices match your search' : 'No devices found';
deviceList.innerHTML = `<p class="no-devices">${message}</p>`;
return;
}
devices.forEach((device, index) => {
const deviceItem = document.createElement('div');
deviceItem.className = 'device-item';
deviceItem.dataset.deviceIndex = index;
deviceItem.dataset.deviceId = device.guid || device.id;
const deviceStatus = getDeviceStatus(device);
const deviceId = device.guid || device.id;
const isProxyActive = activeProxyConnections.has(deviceId);
// Add proxy-active class if this device has an active connection
if (isProxyActive) {
deviceItem.classList.add('proxy-active');
}
deviceItem.innerHTML = `
<div class="device-name">${escapeHtml(device.name || 'Unnamed Device')}</div>
<div class="device-status-dot ${deviceStatus.isOnline ? 'online' : 'offline'}"></div>
`;
// Add click handler for device selection
deviceItem.addEventListener('click', () => selectDevice(device, deviceItem));
deviceList.appendChild(deviceItem);
});
// Update proxy button states after displaying devices
updateProxyButtonStates();
}
// Handle device selection
function selectDevice(device, deviceElement) {
// Remove previous selection
const previousSelected = deviceList.querySelector('.device-item.selected');
if (previousSelected) {
previousSelected.classList.remove('selected');
}
// Select current device
deviceElement.classList.add('selected');
selectedDevice = device;
// Auto-populate device UUID fields
const uuid = device.guid || device.id || '';
deviceUUID.value = uuid;
cookieDeviceUUID.value = uuid; // Also populate cookie proxy UUID field
// Show device selection feedback with connection status
if (uuid) {
const isActive = activeProxyConnections.has(uuid);
const isCookieActive = activeCookieProxyConnections.has(uuid);
const statusText = isActive ? ' (PROXY ACTIVE)' : '';
const cookieStatusText = isCookieActive ? ' (COOKIE PROXY ACTIVE)' : '';
showStatus(connectionStatus, `Selected device: ${device.name || 'Unnamed Device'} (UUID: ${uuid})${statusText}${cookieStatusText}`, 'info');
}
updateProxyButtonStates();
updateCookieProxyButtonStates();
}
// Display detailed device information
function displayDeviceDetails(device) {
const deviceStatus = getDeviceStatus(device);
const detailsHtml = `
<div class="device-details-card">
<div class="device-details-header">
<h3 class="device-details-name">${escapeHtml(device.name || 'Unnamed Device')}</h3>
<span class="device-status ${deviceStatus.isOnline ? 'online' : 'offline'}">
${escapeHtml(deviceStatus.statusText)}
</span>
</div>
<div class="device-details-grid">
<div class="device-detail-item">
<div class="device-detail-label">Device ID</div>
<div class="device-detail-value">${escapeHtml(device.guid || device.id || 'N/A')}</div>
</div>
<div class="device-detail-item">
<div class="device-detail-label">Device Type</div>
<div class="device-detail-value">${escapeHtml(device.type || 'Unknown')}</div>
</div>
<div class="device-detail-item">
<div class="device-detail-label">Model</div>
<div class="device-detail-value">${escapeHtml(device.model || 'Unknown')}</div>
</div>
<div class="device-detail-item">
<div class="device-detail-label">IP Address</div>
<div class="device-detail-value">${escapeHtml(device.ipAddress || 'N/A')}</div>
</div>
<div class="device-detail-item">
<div class="device-detail-label">MAC Address</div>
<div class="device-detail-value">${escapeHtml(device.macAddress || 'N/A')}</div>
</div>
<div class="device-detail-item">
<div class="device-detail-label">Firmware Version</div>
<div class="device-detail-value">${escapeHtml(device.firmwareVersion || 'N/A')}</div>
</div>
<div class="device-detail-item">
<div class="device-detail-label">Serial Number</div>
<div class="device-detail-value">${escapeHtml(device.serialNumber || 'N/A')}</div>
</div>
<div class="device-detail-item">
<div class="device-detail-label">Location</div>
<div class="device-detail-value">${escapeHtml(device.location || 'N/A')}</div>
</div>
</div>
</div>
`;
deviceDetails.innerHTML = detailsHtml;
}
// Utility functions
function showStatus(element, message, type) {
element.textContent = message;
element.className = `status-message ${type}`;
element.style.display = 'block';
}
function setConnectButtonEnabled(enabled) {
connectBtn.disabled = !enabled;
connectBtn.textContent = enabled ? 'Connect to Alta API' : 'Connecting...';
}
function clearDeviceList() {
deviceList.innerHTML = '<p class="placeholder-text">Connect to API to load devices</p>';
selectedDevice = null;
deviceUUID.value = '';
}
// New function to update proxy button states based on active connections
function updateProxyButtonStates() {
if (!sessionData.isConnected) {
startProxyBtn.disabled = true;
stopProxyBtn.disabled = true;
return;
}
const hasActiveConnections = activeProxyConnections.size > 0;
const atMaxConnections = activeProxyConnections.size >= MAX_PROXY_CONNECTIONS;
const selectedDeviceActive = selectedDevice && activeProxyConnections.has(selectedDevice.guid || selectedDevice.id);
// Enable start button if: connected, device selected, not at max connections, and device not already active
startProxyBtn.disabled = !selectedDevice || atMaxConnections || selectedDeviceActive;
// Enable stop button if there are active connections
stopProxyBtn.disabled = !hasActiveConnections;
// Update button text to show connection count
if (hasActiveConnections) {
stopProxyBtn.textContent = `Stop All Proxies (${activeProxyConnections.size})`;
} else {
stopProxyBtn.textContent = 'Stop Proxy';
}
}
function escapeHtml(text) {
if (typeof text !== 'string') return text;
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// Profile Management Functions
async function loadProfiles() {
try {
const result = await window.electronAPI.loadProfiles();
if (result.success) {
currentProfiles = result.profiles;
updateProfileDropdown();
} else {
console.error('Failed to load profiles:', result.message);
}
} catch (error) {
console.error('Error loading profiles:', error);
}
}
function updateProfileDropdown() {
// Clear existing options except the first one
profileSelect.innerHTML = '<option value="">Select a profile...</option>';
currentProfiles.forEach(profile => {
const option = document.createElement('option');
option.value = profile.id;
option.textContent = `${profile.name} (${profile.username})`;
profileSelect.appendChild(option);
});
}
async function handleProfileSelection() {
const selectedProfileId = profileSelect.value;
if (!selectedProfileId) {
selectedProfile = null;
updateButtonStates();
return;
}
try {
const result = await window.electronAPI.getProfile(selectedProfileId);
if (result.success) {
selectedProfile = result.profile;
updateButtonStates();
} else {
showStatus(connectionStatus, `Failed to load profile: ${result.message}`, 'error');
}
} catch (error) {
console.error('Error loading profile:', error);
showStatus(connectionStatus, 'Error loading profile', 'error');
}
}
function showAddProfileModal() {
// Clear the form for new profile
addProfileForm.reset();
addProfileModal.style.display = 'block';
document.getElementById('profileName').focus();
}
function hideAddProfileModal() {
addProfileModal.style.display = 'none';
addProfileForm.reset();
}
async function handleAddProfile(event) {
event.preventDefault();
const name = document.getElementById('profileName').value.trim();
const deploymentUrl = document.getElementById('profileUrl').value.trim();
const username = document.getElementById('profileUsername').value.trim();
const password = document.getElementById('profilePassword').value;
if (!name || !deploymentUrl || !username || !password) {
alert('Please fill in all fields');
return;
}
// Check if profile name already exists
if (currentProfiles.some(p => p.name.toLowerCase() === name.toLowerCase())) {
alert('A profile with this name already exists');
return;
}
try {
const result = await window.electronAPI.saveProfile({
name: name,
deploymentUrl: deploymentUrl.replace(/\/$/, ''), // Remove trailing slash
username: username,
password: password
});
if (result.success) {
hideAddProfileModal();
await loadProfiles(); // Reload profiles
// Select the newly created profile
profileSelect.value = result.profile.id;
await handleProfileSelection();
showStatus(connectionStatus, 'Profile saved successfully!', 'success');
} else {
alert(`Failed to save profile: ${result.message}`);
}
} catch (error) {
console.error('Error saving profile:', error);
alert('Error saving profile');
}
}
function showManageProfilesModal() {
updateProfilesList();
manageProfilesModal.style.display = 'block';
}
function hideManageProfilesModal() {
manageProfilesModal.style.display = 'none';
}
function updateProfilesList() {
profilesList.innerHTML = '';
if (currentProfiles.length === 0) {
profilesList.innerHTML = '<div class="no-profiles">No profiles saved</div>';
return;
}
currentProfiles.forEach(profile => {
const profileItem = document.createElement('div');
profileItem.className = 'profile-item';
profileItem.innerHTML = `
<div class="profile-info">
<h4>${escapeHtml(profile.name)}</h4>
<p>${escapeHtml(profile.username)} @ ${escapeHtml(profile.deploymentUrl)}</p>
</div>
<div class="profile-actions">
<button class="profile-action-btn edit">Edit</button>
<button class="profile-action-btn delete">Delete</button>
</div>
`;
profileItem.querySelector('.edit').addEventListener('click', () => editProfile(profile.id));
profileItem.querySelector('.delete').addEventListener('click', () => deleteProfile(profile.id));
profilesList.appendChild(profileItem);
});
}
async function editProfile(profileId) {
try {
const result = await window.electronAPI.getProfile(profileId);
if (result.success) {
const profile = result.profile;
const newName = prompt('Enter new profile name:', profile.name);
if (newName && newName.trim() !== profile.name) {
const updateResult = await window.electronAPI.updateProfile(profileId, {
name: newName.trim(),
deploymentUrl: profile.deploymentUrl,
username: profile.username,
password: profile.password
});
if (updateResult.success) {
await loadProfiles();
updateProfilesList();
showStatus(connectionStatus, 'Profile updated successfully!', 'success');
} else {
alert(`Failed to update profile: ${updateResult.message}`);
}
}
}
} catch (error) {
console.error('Error editing profile:', error);
alert('Error editing profile');
}
}
async function deleteProfile(profileId) {
const profile = currentProfiles.find(p => p.id === profileId);
if (!profile) return;
if (confirm(`Are you sure you want to delete the profile "${profile.name}"?`)) {
try {
const result = await window.electronAPI.deleteProfile(profileId);
if (result.success) {
await loadProfiles();
updateProfilesList();
// Clear selection if deleted profile was selected
if (profileSelect.value === profileId) {
profileSelect.value = '';
await handleProfileSelection();
}
showStatus(connectionStatus, 'Profile deleted successfully!', 'success');
} else {
alert(`Failed to delete profile: ${result.message}`);
}
} catch (error) {
console.error('Error deleting profile:', error);
alert('Error deleting profile');
}
}
}
// toggleCookieSection is attached via addEventListener in the event listeners section above
// Handle cookie received from Chrome extension via local HTTP bridge
async function handleExtensionCookie(data) {
const { deploymentUrl, cookies, cookieValue } = data;
// If already connected, disconnect first
if (sessionData.isConnected) {
handleDisconnect();
}
// Set session state from extension cookie
sessionData.deploymentUrl = deploymentUrl;
sessionData.cookies = cookies;
sessionData.isConnected = true;
showStatus(connectionStatus, `Connected via Chrome extension to ${deploymentUrl}`, 'success');
updateConnectionStatus(true);
updateButtonStates();
// Auto-populate cookie key and expand cookie proxy section
cookieKey.value = cookieValue;
const cookieContent = document.getElementById('cookieProxyContent');
const cookieIcon = document.getElementById('cookieCollapseIcon');
if (cookieContent.style.display === 'none') {
cookieContent.style.display = 'block';
cookieIcon.textContent = '\u25B2';
cookieIcon.classList.add('expanded');
}
updateCookieProxyButtonStates();
// Fetch devices
try {
await handleGetDevices();
} catch (err) {
console.error('Failed to fetch devices after extension cookie:', err);
showStatus(deviceStatus, 'Connected, but failed to load devices.', 'warning');
}
}
// Initialize the app
document.addEventListener('DOMContentLoaded', async () => {
console.log('Alta Video Camera Proxy loaded');
// Initialize connection status
updateConnectionStatus(false);
updateButtonStates();
// Listen for cookies pushed from Chrome extension
window.electronAPI.onExtensionCookie(handleExtensionCookie);
// Load saved profiles
await loadProfiles();
// Check camera proxy executable availability
await checkCameraProxyAvailability();
});
// Check if camera proxy executable is available
async function checkCameraProxyAvailability() {
try {
const result = await window.electronAPI.checkCameraProxy();
if (!result.exists) {
showStatus(connectionStatus, 'Warning: aware-cam-proxy-win.exe not found. Camera proxy functionality will not work.', 'warning');
// Disable proxy-related buttons
startProxyBtn.disabled = true;
checkVersionBtn.disabled = true;
// Add a tooltip or visual indicator
startProxyBtn.title = 'Camera proxy executable not found';
checkVersionBtn.title = 'Camera proxy executable not found';
} else {
console.log('Camera proxy executable found at:', result.path);
// Optionally get version info
try {
const versionResult = await window.electronAPI.getCameraProxyVersion();
if (versionResult.success) {
console.log('Camera proxy version:', versionResult.version);
}
} catch (error) {
console.log('Could not get camera proxy version:', error);
}
}
} catch (error) {
console.error('Error checking camera proxy availability:', error);
showStatus(connectionStatus, 'Error checking camera proxy availability', 'warning');
}
}
// Toggle cookie section visibility
function toggleCookieSection() {
const content = document.getElementById('cookieProxyContent');
const icon = document.getElementById('cookieCollapseIcon');
if (content.style.display === 'none') {
content.style.display = 'block';
icon.textContent = '▲';
icon.classList.add('expanded');
} else {
content.style.display = 'none';
icon.textContent = '▼';
icon.classList.remove('expanded');
}
}