Skip to main content
Glama
index.ts10.3 kB
const statusEl = document.getElementById("status") as HTMLDivElement; const connectedTabEl = document.getElementById("connected-tab") as HTMLElement; const serverStatusEl = document.getElementById("server-status") as HTMLElement; const serverStatusTextEl = document.getElementById("server-status-text") as HTMLElement; const serverSpinnerEl = document.getElementById("server-spinner") as HTMLSpanElement; const tabInfoContainer = document.getElementById("tab-info") as HTMLDivElement; const nameEl = document.getElementById("tab-name") as HTMLSpanElement; const urlEl = document.getElementById("tab-url") as HTMLAnchorElement; const goToTabButton = document.getElementById("go-to-tab") as HTMLButtonElement; const iconEl = document.getElementById("header-icon") as HTMLImageElement; const connectButton = document.getElementById("connect") as HTMLButtonElement; const disconnectButton = document.getElementById("disconnect") as HTMLButtonElement; const portModeSelect = document.getElementById("port-mode") as HTMLSelectElement; const reconnectButton = document.getElementById("reconnect") as HTMLButtonElement; const manualPortGroup = document.getElementById("port-manual-group") as HTMLDivElement; const portSelect = document.getElementById("port-select") as HTMLSelectElement; const applyPortButton = document.getElementById("apply-port") as HTMLButtonElement; const applyTextEl = document.getElementById("apply-text") as HTMLSpanElement; const applySpinnerEl = document.getElementById("apply-spinner") as HTMLSpanElement; console.log("YetiBrowser popup loaded - NEW VERSION WITH PORT SELECT", { portSelect, applyTextEl, applySpinnerEl }); let lastError: string | null = null; connectButton.addEventListener("click", async () => { lastError = null; try { const activeTab = await getActiveTab(); if (!activeTab || activeTab.id === undefined) { throw new Error("Unable to determine active tab"); } if (!isUrlAllowed(activeTab.url ?? "")) { throw new Error("This page cannot be controlled. Switch to an http(s) tab and try again."); } await chrome.runtime.sendMessage({ type: "yetibrowser/connect", tabId: activeTab.id }); } catch (error) { lastError = error instanceof Error ? error.message : String(error); } finally { await refresh(); } }); disconnectButton.addEventListener("click", async () => { lastError = null; try { await chrome.runtime.sendMessage({ type: "yetibrowser/disconnect" }); } catch (error) { lastError = error instanceof Error ? error.message : String(error); } finally { await refresh(); } }); portModeSelect.addEventListener("change", async () => { const mode = portModeSelect.value === "manual" ? "manual" : "auto"; if (mode === "auto") { manualPortGroup.hidden = true; portSelect.disabled = true; applyPortButton.disabled = true; await applyPortConfiguration(mode); } else { manualPortGroup.hidden = false; portSelect.disabled = false; applyPortButton.disabled = false; portSelect.focus(); } }); applyPortButton.addEventListener("click", async () => { const portValue = Number.parseInt(portSelect.value, 10); if (!Number.isInteger(portValue) || portValue <= 0 || portValue > 65535) { lastError = "Invalid port selected"; await refresh(); return; } await applyPortConfiguration("manual", portValue); }); reconnectButton.addEventListener("click", async () => { lastError = null; reconnectButton.disabled = true; try { const response = await chrome.runtime.sendMessage({ type: "yetibrowser/reconnect" }); if (!response?.ok) { throw new Error(response?.error ?? "Failed to reconnect"); } await waitForSocketConnection(); } catch (error) { lastError = error instanceof Error ? error.message : String(error); } finally { await refresh(); reconnectButton.disabled = false; } }); void refresh(); async function refresh() { const state = await chrome.runtime.sendMessage({ type: "yetibrowser/getState" }); const activeTab = await getActiveTab(); const connectedTab = state?.tabId ? await chrome.tabs .get(state.tabId) .catch(() => undefined) : undefined; updateUi(state, activeTab, connectedTab); } function updateUi( state: { tabId: number | null; socketConnected: boolean; wsPort: number; portMode: PortMode; socketStatus?: SocketStatus; }, activeTab: chrome.tabs.Tab | undefined, connectedTab: chrome.tabs.Tab | undefined, ) { const { tabId, socketConnected, wsPort, portMode, socketStatus = "disconnected" } = state; const activeTabId = activeTab?.id ?? null; const isConnectedToActive = tabId !== null && tabId === activeTabId; connectButton.disabled = !activeTabId || isConnectedToActive || !isUrlAllowed(activeTab?.url ?? ""); disconnectButton.disabled = tabId === null; goToTabButton.disabled = tabId === null; portModeSelect.value = portMode; manualPortGroup.hidden = portMode !== "manual"; portSelect.disabled = portMode !== "manual"; applyPortButton.disabled = portMode !== "manual"; portSelect.value = String(wsPort); if (tabId && connectedTab) { const suffix = isConnectedToActive ? " (current)" : ""; connectedTabEl.textContent = `#${tabId}${suffix}`; connectedTabEl.classList.remove("error"); } else { connectedTabEl.textContent = "None"; connectedTabEl.classList.add("error"); } const modeLabel = portMode === "auto" ? "auto" : "manual"; if (socketStatus === "connecting") { serverStatusTextEl.textContent = `ws://localhost:${wsPort} (${modeLabel}) — connecting…`; serverStatusEl.classList.add("error"); serverSpinnerEl.hidden = false; } else if (socketConnected) { serverStatusTextEl.textContent = `ws://localhost:${wsPort} (${modeLabel})`; serverStatusEl.classList.remove("error"); serverSpinnerEl.hidden = true; } else { serverStatusTextEl.textContent = `ws://localhost:${wsPort} (${modeLabel}) — not connected`; serverStatusEl.classList.add("error"); serverSpinnerEl.hidden = true; } statusEl.classList.remove("error"); if (lastError) { statusEl.textContent = lastError; statusEl.classList.add("error"); } else if (socketStatus === "connecting") { statusEl.textContent = "Scanning for an MCP server…"; } else if (tabId && !isConnectedToActive) { statusEl.textContent = "We’ll interact with this tab even if another is focused."; } else { statusEl.textContent = tabId ? "Connected" : "Not connected"; if (!tabId) { statusEl.classList.add("error"); } } if (connectedTab) { tabInfoContainer.hidden = false; nameEl.textContent = truncate(connectedTab.title ?? "Untitled"); const href = connectedTab.url ?? ""; if (href) { urlEl.textContent = truncate(href, 60); urlEl.href = href; urlEl.hidden = false; } else { urlEl.textContent = ""; urlEl.removeAttribute("href"); urlEl.hidden = true; } } else { tabInfoContainer.hidden = true; nameEl.textContent = ""; urlEl.textContent = ""; urlEl.removeAttribute("href"); urlEl.hidden = true; } iconEl.hidden = false; } async function getActiveTab(): Promise<chrome.tabs.Tab | undefined> { const tabs = await chrome.tabs.query({ active: true, currentWindow: true }); return tabs[0]; } async function applyPortConfiguration(mode: PortMode, port?: number): Promise<void> { lastError = null; showSpinner(true); try { const response = await chrome.runtime.sendMessage({ type: "yetibrowser/setPortConfig", mode, port, }); if (!response?.ok) { throw new Error(response?.error ?? "Failed to update port configuration"); } await waitForSocketConnection(); } catch (error) { lastError = error instanceof Error ? error.message : String(error); } finally { showSpinner(false); await refresh(); } } async function waitForSocketConnection(maxWaitMs = 5000): Promise<void> { const start = Date.now(); let checkCount = 0; while (Date.now() - start < maxWaitMs) { const state = await chrome.runtime.sendMessage({ type: "yetibrowser/getState" }); const activeTab = await getActiveTab(); const connectedTab = state?.tabId ? await chrome.tabs .get(state.tabId) .catch(() => undefined) : undefined; if (state) { updateUi(state, activeTab, connectedTab); } if (state?.socketConnected && state?.socketStatus === "open") { return; } // Start with very fast checks, gradually slow down const delayMs = checkCount < 10 ? 50 : checkCount < 20 ? 100 : 200; checkCount++; await delay(delayMs); } } goToTabButton.addEventListener("click", async () => { const state = await chrome.runtime.sendMessage({ type: "yetibrowser/getState" }); if (!state?.tabId) { return; } try { await chrome.tabs.update(state.tabId, { active: true }); const tab = await chrome.tabs.get(state.tabId); if (tab.windowId !== undefined) { await chrome.windows.update(tab.windowId, { focused: true }); } } catch (error) { lastError = error instanceof Error ? error.message : String(error); await refresh(); } }); function isUrlAllowed(url: string): boolean { if (!url) { return false; } if (url.startsWith("chrome://") || url.startsWith("chrome-extension://")) { return false; } if (url.startsWith("edge://") || url.startsWith("about:")) { return false; } return true; } type PortMode = "auto" | "manual"; type SocketStatus = "disconnected" | "connecting" | "open"; function truncate(value: string, max = 40): string { if (value.length <= max) { return value; } return `${value.slice(0, max - 1)}…`; } function delay(ms: number): Promise<void> { return new Promise((resolve) => { setTimeout(resolve, ms); }); } function showSpinner(show: boolean): void { if (show) { applyTextEl.hidden = true; applySpinnerEl.hidden = false; applyPortButton.disabled = true; reconnectButton.disabled = true; } else { applyTextEl.hidden = false; applySpinnerEl.hidden = true; applyPortButton.disabled = false; reconnectButton.disabled = false; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/yetidevworks/yetibrowser-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server