feat: extension auth + token flow for server-integrated agent

Update Chrome extension to use Bearer auth on all sidebar endpoints:
- background.js captures auth token from /health, exposes via getToken msg
- background.js sets openPanelOnActionClick for direct side panel access
- sidepanel.js gets token from background, sends in all fetch headers
- Health broadcasts include token so sidebar auto-authenticates
- Removes popup from manifest — icon click opens side panel directly

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-03-21 19:31:45 -07:00
parent 9c32e9b2f4
commit 7e8684f923
2 changed files with 31 additions and 10 deletions
+5
View File
@@ -162,6 +162,11 @@ chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
return true;
}
if (msg.type === 'getToken') {
sendResponse({ token: authToken });
return true;
}
if (msg.type === 'fetchRefs') {
fetchAndRelayRefs().then(() => sendResponse({ ok: true }));
return true;
+26 -10
View File
@@ -13,9 +13,17 @@ const OBSERVE_COMMANDS = new Set(['snapshot', 'screenshot', 'diff', 'console', '
let lastId = 0;
let eventSource = null;
let serverUrl = null;
let serverToken = null;
let chatLineCount = 0;
let chatPollInterval = null;
// Auth headers for sidebar endpoints
function authHeaders() {
const h = { 'Content-Type': 'application/json' };
if (serverToken) h['Authorization'] = `Bearer ${serverToken}`;
return h;
}
// ─── Chat ───────────────────────────────────────────────────────
const chatMessages = document.getElementById('chat-messages');
@@ -225,9 +233,10 @@ sendBtn.addEventListener('click', sendMessage);
// Poll for new chat messages
async function pollChat() {
if (!serverUrl) return;
if (!serverUrl || !serverToken) return;
try {
const resp = await fetch(`${serverUrl}/sidebar-chat?after=${chatLineCount}`, {
headers: authHeaders(),
signal: AbortSignal.timeout(3000),
});
if (!resp.ok) return;
@@ -246,7 +255,7 @@ async function pollChat() {
document.getElementById('clear-chat').addEventListener('click', async () => {
if (!serverUrl) return;
try {
await fetch(`${serverUrl}/sidebar-chat/clear`, { method: 'POST' });
await fetch(`${serverUrl}/sidebar-chat/clear`, { method: 'POST', headers: authHeaders() });
} catch {}
// Reset local state
chatLineCount = 0;
@@ -441,8 +450,9 @@ async function fetchRefs() {
// ─── Server Discovery ───────────────────────────────────────────
function updateConnection(url) {
function updateConnection(url, token) {
serverUrl = url;
serverToken = token || null;
if (url) {
document.getElementById('footer-dot').className = 'dot connected';
const port = new URL(url).port;
@@ -490,11 +500,14 @@ portInput.addEventListener('keydown', (e) => {
// Try to connect immediately, retry every 2s until connected
function tryConnect() {
chrome.runtime.sendMessage({ type: 'getServerUrl' }, (resp) => {
if (resp && resp.url) {
updateConnection(resp.url);
chrome.runtime.sendMessage({ type: 'getPort' }, (resp) => {
if (resp && resp.port && resp.connected) {
const url = `http://127.0.0.1:${resp.port}`;
// Get the token from background
chrome.runtime.sendMessage({ type: 'getToken' }, (tokenResp) => {
updateConnection(url, tokenResp?.token);
});
} else {
// Retry in 2s
setTimeout(tryConnect, 2000);
}
});
@@ -505,9 +518,12 @@ tryConnect();
chrome.runtime.onMessage.addListener((msg) => {
if (msg.type === 'health') {
chrome.runtime.sendMessage({ type: 'getServerUrl' }, (resp) => {
updateConnection(msg.data ? resp?.url : null);
});
if (msg.data) {
const url = `http://127.0.0.1:${msg.data.port || 34567}`;
updateConnection(url, msg.data.token);
} else {
updateConnection(null);
}
}
if (msg.type === 'refs') {
if (document.querySelector('.tab[data-tab="refs"].active')) {