'use strict'; const APT_URL = 'http://127.0.0.1:18247/cookie'; const CHALLENGE_URL = 'http://127.0.0.1:18247/challenge'; const PAIRING_STORAGE_KEY = 'aptPairingSecret'; const PAIRING_SECRET_PATTERN = /^[A-Za-z0-9_-]{43}$/; const NONCE_PATTERN = PAIRING_SECRET_PATTERN; const BRIDGE_DEADLINE_MS = 3000; function base64Url(bytes) { let binary = ''; for (const byte of bytes) binary += String.fromCharCode(byte); return btoa(binary).replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/, ''); } async function hmacBase64Url(cryptoApi, secret, value) { const encoder = new TextEncoder(); const key = await cryptoApi.subtle.importKey( 'raw', encoder.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'] ); const signature = await cryptoApi.subtle.sign('HMAC', key, encoder.encode(value)); return base64Url(new Uint8Array(signature)); } function isSupportedDeploymentUrl(value) { try { const url = new URL(value); const host = url.hostname.toLowerCase(); return url.protocol === 'https:' && (host.endsWith('.avasecurity.com') || host.endsWith('.avigilon.com')); } catch { return false; } } function createPopupController({ chromeApi, documentApi, navigatorApi, fetchImpl, confirmCopy, cryptoApi }) { const tabInfo = documentApi.getElementById('tabInfo'); const pairingInfo = documentApi.getElementById('pairingInfo'); const sendBtn = documentApi.getElementById('sendBtn'); const copyBtn = documentApi.getElementById('copyBtn'); const copyWarning = documentApi.getElementById('copyWarning'); const statusMsg = documentApi.getElementById('statusMsg'); const openOptionsBtn = documentApi.getElementById('openOptionsBtn'); let detectedOrigin = null; let pairingSecret = null; let busy = false; async function bridgeFetch(url, options) { const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), BRIDGE_DEADLINE_MS); try { return await fetchImpl(url, { ...options, signal: controller.signal }); } finally { clearTimeout(timer); } } function showStatus(message, type) { statusMsg.textContent = message; statusMsg.className = `status-msg ${type}`; } function updateActions() { const disabled = busy || !detectedOrigin || !pairingSecret; sendBtn.disabled = disabled; copyBtn.disabled = disabled; } function setBusy(value) { busy = value; updateActions(); } async function getVaCookieValue() { if (!detectedOrigin) throw new Error('NO_DEPLOYMENT'); const cookie = await chromeApi.cookies.get({ url: detectedOrigin, name: 'va' }); if (!cookie || !cookie.value) throw new Error('MISSING_COOKIE'); if (cookie.expirationDate && cookie.expirationDate < Date.now() / 1000) { throw new Error('EXPIRED_COOKIE'); } return cookie.value; } function showCookieError(error, copyOperation = false) { if (error && error.message === 'MISSING_COOKIE') { showStatus('No VA token found. Log in to Alta and try again.', 'error'); } else if (error && error.message === 'EXPIRED_COOKIE') { showStatus('The VA token has expired. Log in to Alta again.', 'error'); } else if (copyOperation) { showStatus('Could not copy the VA token. Check clipboard permission.', 'error'); } else { showStatus('Could not reach Alta Proxy Tool on this computer.', 'error'); } } async function sendToApt() { if (!detectedOrigin || !pairingSecret || busy) return; setBusy(true); let cookieValue = null; try { showStatus('Authenticating Alta Proxy Tool...', 'info'); if (!cryptoApi || !cryptoApi.subtle || typeof cryptoApi.getRandomValues !== 'function') { throw new Error('CRYPTO_UNAVAILABLE'); } const clientNonce = base64Url(cryptoApi.getRandomValues(new Uint8Array(32))); const challengeResponse = await bridgeFetch(CHALLENGE_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ clientNonce }) }); const challenge = await challengeResponse.json(); if (!challengeResponse.ok || !challenge || challenge.success !== true || !NONCE_PATTERN.test(challenge.serverNonce) || !NONCE_PATTERN.test(challenge.serverProof)) { throw new Error('BRIDGE_REJECTED'); } const expectedServerProof = await hmacBase64Url( cryptoApi, pairingSecret, JSON.stringify(['apt-server-challenge-v1', clientNonce, challenge.serverNonce]) ); if (challenge.serverProof !== expectedServerProof) throw new Error('BRIDGE_AUTH_FAILED'); showStatus('Sending to Alta Proxy Tool...', 'info'); cookieValue = await getVaCookieValue(); const cookieRequest = { clientNonce, serverNonce: challenge.serverNonce, deploymentUrl: detectedOrigin, cookieValue }; cookieRequest.proof = await hmacBase64Url( cryptoApi, pairingSecret, JSON.stringify(['apt-cookie-request-v1', clientNonce, challenge.serverNonce, detectedOrigin, cookieValue]) ); const response = await bridgeFetch(APT_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(cookieRequest) }); const data = await response.json(); if (!response.ok || !data || data.success !== true) throw new Error('BRIDGE_REJECTED'); showStatus('VA token sent successfully.', 'success'); } catch (error) { showCookieError(error); } finally { cookieValue = null; setBusy(false); } } async function copyToken() { if (!detectedOrigin || !pairingSecret || busy) return; const confirmed = confirmCopy( 'Copy the full VA bearer token? Clipboard history or sync may retain it. Continue only if you will paste it into a trusted destination.' ); if (!confirmed) { showStatus('Copy cancelled. The VA token was not read.', 'info'); return; } setBusy(true); let cookieValue = null; try { cookieValue = await getVaCookieValue(); await navigatorApi.clipboard.writeText(cookieValue); showStatus('VA token copied. Clear your clipboard after use.', 'success'); } catch (error) { showCookieError(error, true); } finally { cookieValue = null; setBusy(false); } } async function init() { copyWarning.textContent = 'Copying exposes the full bearer token. Clipboard history or sync may retain it. A confirmation is required.'; sendBtn.addEventListener('click', sendToApt); copyBtn.addEventListener('click', copyToken); openOptionsBtn.addEventListener('click', () => chromeApi.runtime.openOptionsPage()); const stored = await chromeApi.storage.local.get(PAIRING_STORAGE_KEY); const candidate = stored && stored[PAIRING_STORAGE_KEY]; if (PAIRING_SECRET_PATTERN.test(typeof candidate === 'string' ? candidate : '')) { pairingSecret = candidate; pairingInfo.textContent = 'Paired with Alta Proxy Tool'; pairingInfo.className = 'pairing-info paired'; openOptionsBtn.hidden = true; } else { pairingInfo.textContent = 'Not paired. Add the one-time secret from APT.'; pairingInfo.className = 'pairing-info unpaired'; openOptionsBtn.hidden = false; } const tabs = await chromeApi.tabs.query({ active: true, currentWindow: true }); const tab = tabs && tabs[0]; if (!tab || !isSupportedDeploymentUrl(tab.url)) { tabInfo.textContent = 'This tab is not an Alta deployment.'; tabInfo.className = 'tab-info not-detected'; updateActions(); return; } const url = new URL(tab.url); detectedOrigin = url.origin; tabInfo.textContent = `Detected: ${url.hostname}`; tabInfo.className = 'tab-info detected'; updateActions(); } return { copyToken, init, sendToApt }; } if (typeof module !== 'undefined' && module.exports) { module.exports = { APT_URL, PAIRING_STORAGE_KEY, createPopupController, isSupportedDeploymentUrl }; } else { createPopupController({ chromeApi: chrome, documentApi: document, navigatorApi: navigator, fetchImpl: fetch, confirmCopy: (message) => window.confirm(message), cryptoApi: crypto }).init().catch(() => { const status = document.getElementById('statusMsg'); status.textContent = 'Extension initialization failed. Open pairing settings and try again.'; status.className = 'status-msg error'; }); }