diff --git a/chrome-extension/manifest.json b/chrome-extension/manifest.json
index 7f457cb..fe64b1f 100644
--- a/chrome-extension/manifest.json
+++ b/chrome-extension/manifest.json
@@ -1,14 +1,16 @@
{
"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", "clipboardWrite"],
+ "version": "1.1.0",
+ "description": "Send Alta session cookies to a paired Alta Proxy Tool desktop app.",
+ "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt4EZdkSgOsyiy5DRe0JkX+BpK94FpMjBU59NVIqDPO8QBDwvqNDWT/UjqHK/0aqSxzed5KibX6MdAvc495+u1sCybFdjDdXyBewEvg+PDqGiJketlZKC9dcR1RXHuPgAoM3NaNbMb3TqYcS9J4iGq0UwadxubkQrEcPiuyR6oriOkop8q9/5DWGb15wOGmiCuVmlXfUjNJIvNBm9P/ZHtgFBYDI2PuSSI5GI4j04VFpEyfNlFCrpi8GQ7bYZzezigZWXRjhhNwkx39bNHlkAWYa8XGZseCpKKvi0EaeCoBPjoYSAt161SM1dqX+/UC61/sLOU/SpDB1SYGTm5DC+7wIDAQAB",
+ "permissions": ["cookies", "activeTab", "clipboardWrite", "storage"],
"host_permissions": [
"https://*.avasecurity.com/*",
"https://*.avigilon.com/*",
"http://127.0.0.1:18247/*"
],
+ "options_page": "options.html",
"action": {
"default_popup": "popup.html",
"default_icon": {
diff --git a/chrome-extension/options.css b/chrome-extension/options.css
new file mode 100644
index 0000000..51251b2
--- /dev/null
+++ b/chrome-extension/options.css
@@ -0,0 +1,83 @@
+:root {
+ color-scheme: dark;
+ font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, Roboto, sans-serif;
+}
+
+body {
+ margin: 0;
+ background: #1e1e1e;
+ color: #e0e0e0;
+}
+
+.options-card {
+ max-width: 560px;
+ margin: 48px auto;
+ padding: 28px;
+ border: 1px solid #3c3c3c;
+ border-radius: 8px;
+ background: #252526;
+ box-shadow: 0 12px 32px rgba(0, 0, 0, 0.25);
+}
+
+h1 { margin-top: 0; color: #75afff; }
+h2 { margin-bottom: 6px; font-size: 16px; }
+p { line-height: 1.5; }
+
+label {
+ display: block;
+ margin-bottom: 6px;
+ font-weight: 600;
+}
+
+input {
+ box-sizing: border-box;
+ width: 100%;
+ margin-bottom: 12px;
+ padding: 11px;
+ border: 1px solid #555;
+ border-radius: 4px;
+ background: #1e1e1e;
+ color: #fff;
+ font: inherit;
+}
+
+input:focus {
+ border-color: #0e7afe;
+ outline: 2px solid rgba(14, 122, 254, 0.3);
+}
+
+button {
+ padding: 10px 16px;
+ border-radius: 4px;
+ color: #fff;
+ cursor: pointer;
+ font: inherit;
+ font-weight: 700;
+}
+
+.primary-btn { border: 0; background: #0e7afe; }
+.danger-btn { border: 1px solid #f44336; background: transparent; color: #ff7b72; }
+button:disabled { cursor: not-allowed; opacity: 0.45; }
+
+.status {
+ min-height: 20px;
+ margin-top: 14px;
+ font-weight: 600;
+}
+
+.status.success,
+.paired { color: #7bd67e; }
+.status.error,
+.unpaired { color: #ffc36d; }
+
+.paired-controls {
+ margin-top: 24px;
+ padding-top: 14px;
+ border-top: 1px solid #3c3c3c;
+}
+
+.privacy-note {
+ margin-top: 24px;
+ color: #aaa;
+ font-size: 12px;
+}
diff --git a/chrome-extension/options.html b/chrome-extension/options.html
new file mode 100644
index 0000000..0879681
--- /dev/null
+++ b/chrome-extension/options.html
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+ Pair Alta Proxy Tool
+
+
+
+
+ Pair with Alta Proxy Tool
+ In the desktop app, create a one-time pairing secret. Paste it below on this computer. Treat it like a password.
+
+
+
+ Current pairing
+ Checking...
+
+
+ The secret is stored only in Chrome extension local storage. It is never displayed again or copied automatically.
+
+
+
+
diff --git a/chrome-extension/options.js b/chrome-extension/options.js
new file mode 100644
index 0000000..30c7ce2
--- /dev/null
+++ b/chrome-extension/options.js
@@ -0,0 +1,61 @@
+'use strict';
+
+const PAIRING_STORAGE_KEY = 'aptPairingSecret';
+const PAIRING_SECRET_PATTERN = /^[A-Za-z0-9_-]{43}$/;
+
+const pairingForm = document.getElementById('pairingForm');
+const pairingSecretInput = document.getElementById('pairingSecret');
+const pairingStatus = document.getElementById('pairingStatus');
+const currentState = document.getElementById('currentState');
+const forgetBtn = document.getElementById('forgetBtn');
+
+function setStatus(message, type) {
+ pairingStatus.textContent = message;
+ pairingStatus.className = `status ${type}`;
+}
+
+function renderPairingState(paired) {
+ currentState.textContent = paired ? 'Paired on this Chrome profile.' : 'Not paired.';
+ currentState.className = paired ? 'paired' : 'unpaired';
+ forgetBtn.disabled = !paired;
+}
+
+async function refreshPairingState() {
+ const stored = await chrome.storage.local.get(PAIRING_STORAGE_KEY);
+ const candidate = stored && stored[PAIRING_STORAGE_KEY];
+ renderPairingState(PAIRING_SECRET_PATTERN.test(typeof candidate === 'string' ? candidate : ''));
+}
+
+pairingForm.addEventListener('submit', async (event) => {
+ event.preventDefault();
+ const candidate = pairingSecretInput.value.trim();
+ pairingSecretInput.value = '';
+
+ if (!PAIRING_SECRET_PATTERN.test(candidate)) {
+ setStatus('That secret is not valid. Generate a new pairing secret in APT and paste it exactly.', 'error');
+ return;
+ }
+
+ try {
+ await chrome.storage.local.set({ [PAIRING_STORAGE_KEY]: candidate });
+ setStatus('Pairing saved. You can now use Send to APT from an Alta tab.', 'success');
+ renderPairingState(true);
+ } catch {
+ setStatus('Chrome could not save the pairing. Try again.', 'error');
+ }
+});
+
+forgetBtn.addEventListener('click', async () => {
+ try {
+ await chrome.storage.local.remove(PAIRING_STORAGE_KEY);
+ renderPairingState(false);
+ setStatus('Pairing forgotten. Revoke or rotate it in APT as well.', 'success');
+ } catch {
+ setStatus('Chrome could not forget the pairing. Try again.', 'error');
+ }
+});
+
+refreshPairingState().catch(() => {
+ renderPairingState(false);
+ setStatus('Chrome could not read pairing state.', 'error');
+});
diff --git a/chrome-extension/popup.css b/chrome-extension/popup.css
index e4c5e8d..6f4387b 100644
--- a/chrome-extension/popup.css
+++ b/chrome-extension/popup.css
@@ -2,120 +2,131 @@
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;
+ min-width: 320px;
+ background: #1e1e1e;
+ color: #e0e0e0;
+ font: 14px/1.4 'Segoe UI', -apple-system, BlinkMacSystemFont, Roboto, sans-serif;
}
-.popup-container {
- padding: 16px;
-}
+.popup-container { padding: 16px; }
h1 {
+ margin: 0 0 12px;
+ color: #0e7afe;
font-size: 16px;
font-weight: 600;
- color: #0E7AFE;
- margin: 0 0 12px 0;
+ letter-spacing: 0.5px;
text-align: center;
text-transform: uppercase;
- letter-spacing: 0.5px;
}
-.tab-info {
- background: #2D2D30;
- border: 1px solid #3C3C3C;
+.tab-info,
+.pairing-info {
+ margin-bottom: 10px;
+ padding: 9px 11px;
+ border: 1px solid #3c3c3c;
border-radius: 4px;
- padding: 10px 12px;
- margin-bottom: 12px;
+ background: #2d2d30;
+ color: #aaa;
font-size: 13px;
- color: #999999;
- word-break: break-all;
+ overflow-wrap: anywhere;
}
-.tab-info.detected {
- color: #4CAF50;
- border-color: #4CAF50;
+.tab-info.detected,
+.pairing-info.paired {
+ border-color: #4caf50;
+ color: #7bd67e;
}
-.tab-info.not-detected {
- color: #F44336;
- border-color: #F44336;
+.tab-info.not-detected,
+.pairing-info.unpaired {
+ border-color: #f0a33a;
+ color: #ffc36d;
}
-.action-buttons {
- display: grid;
- gap: 8px;
-}
+.action-buttons { display: grid; gap: 8px; }
.primary-btn,
-.secondary-btn {
- display: block;
+.secondary-btn,
+.link-btn {
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;
+ font-family: inherit;
+ font-size: 14px;
+ font-weight: 700;
}
.primary-btn {
- background: #0E7AFE;
+ border: 0;
+ background: #0e7afe;
+ color: #fff;
}
.secondary-btn {
- background: #2D2D30;
- border: 1px solid #0E7AFE;
- color: #E0E0E0;
+ border: 1px solid #0e7afe;
+ background: #2d2d30;
+ color: #e0e0e0;
}
-.primary-btn:hover:not(:disabled) {
- background: #0A5FD9;
+.link-btn {
+ margin: -2px 0 10px;
+ padding: 5px;
+ border: 0;
+ background: transparent;
+ color: #75afff;
+ text-decoration: underline;
}
-.secondary-btn:hover:not(:disabled) {
- background: #0E7AFE;
-}
+.primary-btn:hover:not(:disabled) { background: #0a5fd9; }
+.secondary-btn:hover:not(:disabled) { background: #0e7afe; }
.primary-btn:disabled,
.secondary-btn:disabled {
- opacity: 0.5;
cursor: not-allowed;
+ opacity: 0.45;
+}
+
+.copy-warning {
+ margin: 10px 0 0;
+ padding: 8px 10px;
+ border-left: 3px solid #f0a33a;
+ background: rgba(240, 163, 58, 0.08);
+ color: #d7c29f;
+ font-size: 12px;
}
.status-msg {
+ display: none;
margin-top: 10px;
padding: 8px 12px;
+ border: 1px solid transparent;
border-radius: 4px;
font-size: 13px;
- font-weight: bold;
- display: none;
- border: 1px solid transparent;
+ font-weight: 700;
}
+.status-msg.success,
+.status-msg.error,
+.status-msg.info { display: block; }
+
.status-msg.success {
- display: block;
+ border-color: #4caf50;
background: rgba(76, 175, 80, 0.1);
- color: #4CAF50;
- border-color: #4CAF50;
+ color: #7bd67e;
}
.status-msg.error {
- display: block;
+ border-color: #f44336;
background: rgba(244, 67, 54, 0.1);
- color: #F44336;
- border-color: #F44336;
+ color: #ff7b72;
}
.status-msg.info {
- display: block;
+ border-color: #0e7afe;
background: rgba(14, 122, 254, 0.1);
- color: #0E7AFE;
- border-color: #0E7AFE;
+ color: #75afff;
}
+
+[hidden] { display: none !important; }
diff --git a/chrome-extension/popup.html b/chrome-extension/popup.html
index a1e04a2..a859867 100644
--- a/chrome-extension/popup.html
+++ b/chrome-extension/popup.html
@@ -3,19 +3,23 @@
+
Alta Proxy Tool Bridge
-
+ Copying exposes the full bearer token. Clipboard history or sync may retain it. A confirmation is required.
+
+