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>
This commit is contained in:
@@ -4,7 +4,13 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||
|
||||
## Project Overview
|
||||
|
||||
Alta Video Camera Proxy — an Electron desktop app that authenticates with Avigilon Alta Video deployments, discovers cameras, and launches external proxy executables (`aware-cam-proxy-win.exe`, `aware-cam-proxy.exe`) to establish camera connections. Windows-only due to the proxy executables.
|
||||
Alta Proxy Tool (APT) — an Electron desktop app that authenticates with Avigilon Alta Video deployments, discovers cameras, and launches external proxy executables (`aware-cam-proxy-win.exe`, `aware-cam-proxy.exe`) to establish camera connections. Windows-only due to the proxy executables.
|
||||
|
||||
## Repository
|
||||
|
||||
- **GitHub**: https://github.com/PageZ948/Alta-Proxy-Tool (private)
|
||||
- **Branch**: master
|
||||
- **Git identity**: Zac <zpage948@gmail.com> (repo-local config)
|
||||
|
||||
## Commands
|
||||
|
||||
@@ -19,26 +25,43 @@ No test framework is configured. No linter is configured.
|
||||
|
||||
## Architecture
|
||||
|
||||
This is a vanilla Electron app (no React/Vue/framework). Four files form the entire application:
|
||||
This is a vanilla Electron app (no React/Vue/framework). Core files:
|
||||
|
||||
```
|
||||
main.js → Electron main process: IPC handlers, API calls (axios), profile CRUD,
|
||||
camera proxy process spawning, password encryption (CryptoJS + machine-derived key)
|
||||
camera proxy process spawning, password encryption (CryptoJS + machine-derived key),
|
||||
local HTTP cookie server for Chrome extension bridge
|
||||
preload.js → contextBridge exposing window.electronAPI with typed IPC invoke wrappers
|
||||
renderer.js → All UI logic: DOM manipulation, state management, event handlers
|
||||
index.html → Static HTML shell, no inline scripts (CSP enforced)
|
||||
styles.css → Dark theme using CSS custom properties
|
||||
```
|
||||
|
||||
A companion Chrome extension lives in `chrome-extension/`:
|
||||
|
||||
```
|
||||
chrome-extension/
|
||||
manifest.json → Manifest V3, cookies + activeTab permissions
|
||||
popup.html → Extension popup UI
|
||||
popup.css → Dark theme matching the Electron app
|
||||
popup.js → Tab detection, cookie retrieval, POST to localhost
|
||||
icon*.png → Placeholder icons
|
||||
```
|
||||
|
||||
### IPC Communication Pattern
|
||||
|
||||
All cross-process communication follows one pattern:
|
||||
Most cross-process communication follows the request/response pattern:
|
||||
1. `main.js` registers handler: `ipcMain.handle('channel-name', async (event, params) => { ... })`
|
||||
2. `preload.js` exposes it: `channelName: (params) => ipcRenderer.invoke('channel-name', params)`
|
||||
3. `renderer.js` calls it: `const result = await window.electronAPI.channelName(params)`
|
||||
|
||||
All handlers return `{ success: boolean, message?: string, ...data }`.
|
||||
|
||||
There is one **push-pattern** channel for the Chrome extension cookie bridge:
|
||||
- `main.js` sends: `mainWindow.webContents.send('extension-cookie-received', data)`
|
||||
- `preload.js` bridges: `ipcRenderer.on('extension-cookie-received', callback)`
|
||||
- `renderer.js` listens via `window.electronAPI.onExtensionCookie(callback)`
|
||||
|
||||
### IPC Channels
|
||||
|
||||
| Channel | Purpose |
|
||||
@@ -52,6 +75,7 @@ All handlers return `{ success: boolean, message?: string, ...data }`.
|
||||
| `camera-proxy-stop` | Kills all proxy processes via taskkill/powershell |
|
||||
| `camera-proxy-check` | Checks if proxy executable exists |
|
||||
| `camera-proxy-version` | Runs proxy with -v flag |
|
||||
| `extension-cookie-received` | Push channel: cookie data from Chrome extension → renderer |
|
||||
|
||||
### State Management (renderer.js)
|
||||
|
||||
@@ -68,11 +92,25 @@ Active proxy processes are tracked in two Maps: `activeProxyConnections` and `ac
|
||||
- Legacy profiles auto-migrate from old static key on first load
|
||||
- Clipboard is cleared 30 seconds after password copy
|
||||
- Passwords never written to DOM; kept only in JS variables (`selectedProfile`)
|
||||
- Local HTTP cookie server (port 18247) bound to `127.0.0.1` only
|
||||
- Cookie server validates: shared token header, CORS restricted to `chrome-extension://` origins, deployment URL must be `*.avasecurity.com` or `*.avigilon.com` over HTTPS, type/length limits on all inputs, 64KB body size limit
|
||||
|
||||
### Profile Storage
|
||||
|
||||
Profiles stored at `~/.alta-api-profiles.json`. Passwords encrypted with CryptoJS AES using a machine-derived key. The `profiles-load` handler strips passwords before sending to renderer; `profiles-get` decrypts for a specific profile when needed.
|
||||
|
||||
### Chrome Extension Cookie Bridge
|
||||
|
||||
Users already logged into Alta in Chrome can send their `va` session cookie to the running Electron app. The flow:
|
||||
|
||||
1. Chrome extension popup detects Alta tab (`*.avasecurity.com` / `*.avigilon.com`)
|
||||
2. User clicks "Send Cookie to APT"
|
||||
3. Extension POSTs `{deploymentUrl, cookieValue}` to `http://127.0.0.1:18247/cookie` with `X-APT-Token` header
|
||||
4. `main.js` HTTP server validates and forwards via IPC push to renderer
|
||||
5. `renderer.js` `handleExtensionCookie()` sets session state, populates cookie key, expands cookie proxy section, fetches devices
|
||||
|
||||
The extension is loaded unpacked via `chrome://extensions/` → Developer mode → Load unpacked → select `chrome-extension/`.
|
||||
|
||||
## Key Conventions
|
||||
|
||||
- No inline event handlers in HTML — all use `addEventListener` in renderer.js
|
||||
@@ -86,4 +124,4 @@ Profiles stored at `~/.alta-api-profiles.json`. Passwords encrypted with CryptoJ
|
||||
- `aware-cam-proxy-win.exe` — username/password auth proxy (required)
|
||||
- `aware-cam-proxy.exe` — cookie-based auth proxy (optional)
|
||||
|
||||
These are not bundled via npm. They must be in the app root directory.
|
||||
These are not bundled via npm. They must be in the app root directory. They are gitignored along with `*.pdf`, `node_modules/`, and `dist/`.
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 579 B |
Binary file not shown.
|
After Width: | Height: | Size: 117 B |
Binary file not shown.
|
After Width: | Height: | Size: 225 B |
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "Alta Proxy Tool Bridge",
|
||||
"version": "1.0.0",
|
||||
"description": "Send Alta session cookies to the Alta Proxy Tool desktop app.",
|
||||
"permissions": ["cookies", "activeTab"],
|
||||
"host_permissions": [
|
||||
"https://*.avasecurity.com/*",
|
||||
"https://*.avigilon.com/*",
|
||||
"http://127.0.0.1:18247/*"
|
||||
],
|
||||
"action": {
|
||||
"default_popup": "popup.html",
|
||||
"default_icon": {
|
||||
"16": "icon16.png",
|
||||
"48": "icon48.png",
|
||||
"128": "icon128.png"
|
||||
}
|
||||
},
|
||||
"icons": {
|
||||
"16": "icon16.png",
|
||||
"48": "icon48.png",
|
||||
"128": "icon128.png"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/* Dark theme matching the Electron app */
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, Roboto, sans-serif;
|
||||
background: #1E1E1E;
|
||||
color: #E0E0E0;
|
||||
font-size: 14px;
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
.popup-container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #0E7AFE;
|
||||
margin: 0 0 12px 0;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.tab-info {
|
||||
background: #2D2D30;
|
||||
border: 1px solid #3C3C3C;
|
||||
border-radius: 4px;
|
||||
padding: 10px 12px;
|
||||
margin-bottom: 12px;
|
||||
font-size: 13px;
|
||||
color: #999999;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.tab-info.detected {
|
||||
color: #4CAF50;
|
||||
border-color: #4CAF50;
|
||||
}
|
||||
|
||||
.tab-info.not-detected {
|
||||
color: #F44336;
|
||||
border-color: #F44336;
|
||||
}
|
||||
|
||||
.send-btn {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 10px 16px;
|
||||
font-family: inherit;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
background: #0E7AFE;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
|
||||
.send-btn:hover:not(:disabled) {
|
||||
background: #0A5FD9;
|
||||
}
|
||||
|
||||
.send-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.status-msg {
|
||||
margin-top: 10px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
display: none;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.status-msg.success {
|
||||
display: block;
|
||||
background: rgba(76, 175, 80, 0.1);
|
||||
color: #4CAF50;
|
||||
border-color: #4CAF50;
|
||||
}
|
||||
|
||||
.status-msg.error {
|
||||
display: block;
|
||||
background: rgba(244, 67, 54, 0.1);
|
||||
color: #F44336;
|
||||
border-color: #F44336;
|
||||
}
|
||||
|
||||
.status-msg.info {
|
||||
display: block;
|
||||
background: rgba(14, 122, 254, 0.1);
|
||||
color: #0E7AFE;
|
||||
border-color: #0E7AFE;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; connect-src http://127.0.0.1:18247;">
|
||||
<title>Alta Proxy Tool Bridge</title>
|
||||
<link rel="stylesheet" href="popup.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="popup-container">
|
||||
<h1>Alta Proxy Tool</h1>
|
||||
<div id="tabInfo" class="tab-info">Checking tab...</div>
|
||||
<button id="sendBtn" class="send-btn" disabled>Send Cookie to APT</button>
|
||||
<div id="statusMsg" class="status-msg"></div>
|
||||
</div>
|
||||
<script src="popup.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,100 @@
|
||||
const APT_URL = 'http://127.0.0.1:18247/cookie';
|
||||
const APT_TOKEN = 'apt-local-bridge-token';
|
||||
|
||||
const tabInfo = document.getElementById('tabInfo');
|
||||
const sendBtn = document.getElementById('sendBtn');
|
||||
const statusMsg = document.getElementById('statusMsg');
|
||||
|
||||
let detectedOrigin = null;
|
||||
|
||||
function showStatus(message, type) {
|
||||
statusMsg.textContent = message;
|
||||
statusMsg.className = 'status-msg ' + type;
|
||||
}
|
||||
|
||||
// Check the active tab on popup open
|
||||
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
|
||||
if (!tabs || tabs.length === 0) {
|
||||
tabInfo.textContent = 'No active tab found.';
|
||||
tabInfo.className = 'tab-info not-detected';
|
||||
return;
|
||||
}
|
||||
|
||||
const tab = tabs[0];
|
||||
let url;
|
||||
try {
|
||||
url = new URL(tab.url);
|
||||
} catch {
|
||||
tabInfo.textContent = 'Cannot read this tab URL.';
|
||||
tabInfo.className = 'tab-info not-detected';
|
||||
return;
|
||||
}
|
||||
|
||||
const hostname = url.hostname;
|
||||
const isAlta = hostname.endsWith('.avasecurity.com') || hostname.endsWith('.avigilon.com');
|
||||
|
||||
if (!isAlta) {
|
||||
tabInfo.textContent = 'This tab is not an Alta deployment.';
|
||||
tabInfo.className = 'tab-info not-detected';
|
||||
return;
|
||||
}
|
||||
|
||||
detectedOrigin = url.origin;
|
||||
tabInfo.textContent = 'Detected: ' + hostname;
|
||||
tabInfo.className = 'tab-info detected';
|
||||
sendBtn.disabled = false;
|
||||
});
|
||||
|
||||
// Send cookie on button click
|
||||
sendBtn.addEventListener('click', async () => {
|
||||
if (!detectedOrigin) return;
|
||||
|
||||
sendBtn.disabled = true;
|
||||
showStatus('Retrieving cookie...', 'info');
|
||||
|
||||
try {
|
||||
const cookie = await chrome.cookies.get({ url: detectedOrigin, name: 'va' });
|
||||
|
||||
if (!cookie || !cookie.value) {
|
||||
showStatus('No "va" session cookie found. Are you logged in?', 'error');
|
||||
sendBtn.disabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (cookie.expirationDate && cookie.expirationDate < Date.now() / 1000) {
|
||||
showStatus('Session cookie has expired. Please log in again.', 'error');
|
||||
sendBtn.disabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
showStatus('Sending to Alta Proxy Tool...', 'info');
|
||||
|
||||
const response = await fetch(APT_URL, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-APT-Token': APT_TOKEN
|
||||
},
|
||||
body: JSON.stringify({
|
||||
deploymentUrl: detectedOrigin,
|
||||
cookieValue: cookie.value
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
showStatus('Cookie sent successfully!', 'success');
|
||||
} else {
|
||||
showStatus('Error: ' + (data.message || 'Unknown error'), 'error');
|
||||
sendBtn.disabled = false;
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.message && err.message.includes('Failed to fetch')) {
|
||||
showStatus('Alta Proxy Tool is not running.', 'error');
|
||||
} else {
|
||||
showStatus('Error: ' + err.message, 'error');
|
||||
}
|
||||
sendBtn.disabled = false;
|
||||
}
|
||||
});
|
||||
@@ -6,9 +6,13 @@ const axios = require('axios');
|
||||
const CryptoJS = require('crypto-js');
|
||||
const { spawn } = require('child_process');
|
||||
const crypto = require('crypto');
|
||||
const http = require('http');
|
||||
|
||||
let mainWindow;
|
||||
let activeProxyProcesses = new Map(); // Track active camera proxy processes
|
||||
let cookieServer = null;
|
||||
const COOKIE_SERVER_PORT = 18247;
|
||||
const COOKIE_SERVER_TOKEN = 'apt-local-bridge-token';
|
||||
|
||||
// Sanitize strings before embedding in batch files to prevent command injection
|
||||
function sanitizeBatchInput(input) {
|
||||
@@ -113,6 +117,121 @@ function saveProfiles(profiles) {
|
||||
}
|
||||
}
|
||||
|
||||
function startCookieServer() {
|
||||
cookieServer = http.createServer((req, res) => {
|
||||
// CORS headers — only allow Chrome extension origins
|
||||
const origin = req.headers.origin || '';
|
||||
if (origin.startsWith('chrome-extension://')) {
|
||||
res.setHeader('Access-Control-Allow-Origin', origin);
|
||||
}
|
||||
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
|
||||
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-APT-Token');
|
||||
|
||||
// Handle preflight
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.writeHead(204);
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
// Only accept POST /cookie
|
||||
if (req.method !== 'POST' || req.url !== '/cookie') {
|
||||
res.writeHead(404, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ success: false, message: 'Not found' }));
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify shared token
|
||||
if (req.headers['x-apt-token'] !== COOKIE_SERVER_TOKEN) {
|
||||
res.writeHead(403, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ success: false, message: 'Forbidden' }));
|
||||
return;
|
||||
}
|
||||
|
||||
// Read body with 64KB size limit
|
||||
let body = '';
|
||||
let bodySize = 0;
|
||||
const MAX_BODY_SIZE = 65536;
|
||||
|
||||
req.on('data', (chunk) => {
|
||||
bodySize += chunk.length;
|
||||
if (bodySize > MAX_BODY_SIZE) {
|
||||
res.writeHead(413, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ success: false, message: 'Payload too large' }));
|
||||
req.destroy();
|
||||
return;
|
||||
}
|
||||
body += chunk;
|
||||
});
|
||||
|
||||
req.on('end', () => {
|
||||
try {
|
||||
const data = JSON.parse(body);
|
||||
const { deploymentUrl, cookieValue } = data;
|
||||
|
||||
if (!deploymentUrl || !cookieValue) {
|
||||
res.writeHead(400, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ success: false, message: 'Missing deploymentUrl or cookieValue' }));
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate types and lengths
|
||||
if (typeof deploymentUrl !== 'string' || typeof cookieValue !== 'string' ||
|
||||
deploymentUrl.length > 512 || cookieValue.length > 4096) {
|
||||
res.writeHead(400, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ success: false, message: 'Invalid parameter types or lengths' }));
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate deployment URL is an Alta domain
|
||||
try {
|
||||
const parsed = new URL(deploymentUrl);
|
||||
const isAltaDomain = parsed.hostname.endsWith('.avasecurity.com') ||
|
||||
parsed.hostname.endsWith('.avigilon.com');
|
||||
if (!isAltaDomain || parsed.protocol !== 'https:') {
|
||||
res.writeHead(400, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ success: false, message: 'Invalid deployment URL domain' }));
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
res.writeHead(400, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ success: false, message: 'Invalid deployment URL' }));
|
||||
return;
|
||||
}
|
||||
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
const cookies = ['va=' + cookieValue];
|
||||
mainWindow.webContents.send('extension-cookie-received', {
|
||||
deploymentUrl: deploymentUrl.replace(/\/$/, ''),
|
||||
cookies,
|
||||
cookieValue
|
||||
});
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ success: true, message: 'Cookie received' }));
|
||||
} else {
|
||||
res.writeHead(503, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ success: false, message: 'Application window not available' }));
|
||||
}
|
||||
} catch (e) {
|
||||
res.writeHead(400, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ success: false, message: 'Invalid JSON' }));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
cookieServer.listen(COOKIE_SERVER_PORT, '127.0.0.1', () => {
|
||||
console.log(`Cookie server listening on http://127.0.0.1:${COOKIE_SERVER_PORT}`);
|
||||
});
|
||||
|
||||
cookieServer.on('error', (err) => {
|
||||
if (err.code === 'EADDRINUSE') {
|
||||
console.error(`Cookie server error: Port ${COOKIE_SERVER_PORT} is already in use`);
|
||||
} else {
|
||||
console.error('Cookie server error:', err.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function createWindow() {
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1400,
|
||||
@@ -134,7 +253,16 @@ function createWindow() {
|
||||
}
|
||||
}
|
||||
|
||||
app.whenReady().then(createWindow);
|
||||
app.whenReady().then(() => {
|
||||
createWindow();
|
||||
startCookieServer();
|
||||
});
|
||||
|
||||
app.on('before-quit', () => {
|
||||
if (cookieServer) {
|
||||
cookieServer.close();
|
||||
}
|
||||
});
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
|
||||
+12
-1
@@ -20,5 +20,16 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||
stopCameraProxy: (processId) => ipcRenderer.invoke('camera-proxy-stop', { processId }),
|
||||
checkCameraProxy: () => ipcRenderer.invoke('camera-proxy-check'),
|
||||
getCameraProxyVersion: () => ipcRenderer.invoke('camera-proxy-version'),
|
||||
listActiveCameraProxies: () => ipcRenderer.invoke('camera-proxy-list-active')
|
||||
listActiveCameraProxies: () => ipcRenderer.invoke('camera-proxy-list-active'),
|
||||
|
||||
// Extension cookie bridge (push from main process)
|
||||
onExtensionCookie: (callback) => {
|
||||
ipcRenderer.on('extension-cookie-received', (event, data) => {
|
||||
try {
|
||||
callback(data);
|
||||
} catch (error) {
|
||||
console.error('Extension cookie handler error:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
+41
@@ -996,6 +996,44 @@ async function deleteProfile(profileId) {
|
||||
|
||||
// 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');
|
||||
@@ -1004,6 +1042,9 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
updateConnectionStatus(false);
|
||||
updateButtonStates();
|
||||
|
||||
// Listen for cookies pushed from Chrome extension
|
||||
window.electronAPI.onExtensionCookie(handleExtensionCookie);
|
||||
|
||||
// Load saved profiles
|
||||
await loadProfiles();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user