Files
MuyueWorkspace/extension/src/entrypoints/sidepanel/main.js
Augustin 9f9f2bd2c6
Some checks failed
Beta Release / beta (push) Failing after 48s
feat(extension): browser extension for Chrome/Edge/Firefox + CI + v0.8.0
Adds a WXT-based browser extension that replaces manual JS snippet
injection for AI-driven browser testing. The extension auto-connects
to the Muyue server via WebSocket on every page, using the exact
same protocol as the existing snippet — zero backend changes needed.

- Chrome/Edge (MV3) + Firefox (MV2) from single codebase via WXT
- Content script: auto-connect WS, console capture, URL tracking, RPC
- Background service worker: token management, screenshots, badge
- Popup + side panel with server status, sessions, URL config
- CI workflows: build extension, attach .zip to releases
- Makefile targets: ext, ext-chrome, ext-firefox, ext-zip
- Version bumped to 0.8.0

Assisted-by: GLM-5.1 via Crush <crush@charm.land>
2026-04-27 16:50:04 +02:00

73 lines
2.2 KiB
JavaScript

import '../../styles/panel.css';
import { getServerUrl, setServerUrl, fetchSessions } from '../../lib/config';
const $serverStatus = document.getElementById('server-status');
const $sessionCount = document.getElementById('session-count');
const $errorCount = document.getElementById('error-count');
const $sessionsList = document.getElementById('sessions-list');
const $btnDashboard = document.getElementById('btn-dashboard');
const $serverUrl = document.getElementById('server-url');
const $btnSaveUrl = document.getElementById('btn-save-url');
function dot(color) {
return `<span class="dot dot-${color}"></span>`;
}
function renderSessions(sessions) {
if (sessions.length === 0) {
$sessionsList.innerHTML = '';
return;
}
$sessionsList.innerHTML = `
<div class="status-card" style="margin-top:12px">
<div style="font-size:11px;color:var(--text-secondary);margin-bottom:8px;text-transform:uppercase;letter-spacing:0.5px">
Connected tabs
</div>
${sessions.map((s) => `
<div class="status-row">
<span style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:200px" title="${s.url}">
${s.title || s.url || s.id}
</span>
<span style="font-size:10px;color:var(--text-secondary);font-family:var(--font-mono)">
${s.id.slice(0, 8)}
</span>
</div>
`).join('')}
</div>
`;
}
async function refresh() {
const url = await getServerUrl();
$serverUrl.value = url;
$btnDashboard.href = url;
try {
const sessions = await fetchSessions();
$serverStatus.innerHTML = `${dot('green')} Online`;
$sessionCount.textContent = sessions.length;
renderSessions(sessions);
} catch {
$serverStatus.innerHTML = `${dot('red')} Offline`;
$sessionCount.textContent = '—';
$sessionsList.innerHTML = '';
}
chrome.runtime.sendMessage({ type: 'get_state' }, (state) => {
if (chrome.runtime.lastError || !state) return;
$errorCount.textContent = state.errorCount || 0;
});
}
$btnSaveUrl.addEventListener('click', async () => {
const url = $serverUrl.value.trim().replace(/\/$/, '');
if (url) {
await setServerUrl(url);
refresh();
}
});
refresh();
setInterval(refresh, 5000);