Files
playwriter/extension-kapture/background.js
Tommy D. Rossi ec02a90812 adding examples
2025-11-13 19:42:44 +01:00

240 lines
6.8 KiB
JavaScript

// Background service worker - manages WebSocket connections
import { TabManager } from './modules/tab-manager.js';
import {ConsoleLogEntry} from "./modules/models.js";
// Single source of truth for all tab state
const tabManager = new TabManager();
// Helper function to send connection state to content script
function sendConnectionStateToTab(tabId, connectionState) {
chrome.tabs.sendMessage(tabId, {
command: '_connectionStateChanged',
params: {
status: connectionState.status,
connected: connectionState.connected
}
}).catch(err => {
// Content script might not be injected yet, ignore error
console.debug('Could not send connection state to tab:', err);
});
}
// Listen for tab state changes
tabManager.addListener((tabId, event, tabState, data) => {
switch (event) {
case 'stateChanged':
// Broadcast to all ports for this tab
tabState.broadcastToPorts({
type: 'state',
tabId,
...tabState.getConnectionState()
});
// Send connection state to content script
const connectionState = tabState.getConnectionState();
sendConnectionStateToTab(tabId, connectionState);
// Update action badge for active tab
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
if (tabs[0]?.id === tabId) {
updateActionBadge(tabState.getConnectionState());
}
});
break;
case 'messageReceived':
case 'messageSent':
// Broadcast updated messages to all ports
tabState.broadcastToPorts({
type: 'messages',
tabId,
messages: tabState.getMessages()
});
break;
case 'consoleLogAdded':
// Broadcast updated console count
tabState.broadcastToPorts({
type: 'consoleCount',
tabId,
count: tabState.getConsoleLogCount()
});
break;
case 'messagesCleared':
tabState.broadcastToPorts({
type: 'messages',
tabId,
messages: []
});
break;
case 'consoleLogsCleared':
tabState.broadcastToPorts({
type: 'consoleCount',
tabId,
count: 0
});
break;
}
});
// Update action badge based on connection status
function updateActionBadge(state) {
switch (state.status) {
case 'connected':
chrome.action.setBadgeText({ text: '✓' });
chrome.action.setBadgeBackgroundColor({ color: '#4caf50' });
break;
case 'retrying':
chrome.action.setBadgeText({ text: '↻' });
chrome.action.setBadgeBackgroundColor({ color: '#ff9800' });
break;
case 'disconnected':
default:
chrome.action.setBadgeText({ text: '' });
break;
}
}
// Handle UI connections
chrome.runtime.onConnect.addListener((port) => {
port.onMessage.addListener((msg) => {
if (msg.type === 'subscribe' && msg.tabId) {
tabManager.addPort(msg.tabId, port);
} else if (msg.type === 'clearMessages' && msg.tabId) {
tabManager.clearMessages(msg.tabId);
} else if (msg.type === 'clearConsoleLogs' && msg.tabId) {
tabManager.clearConsoleLogs(msg.tabId);
}
});
port.onDisconnect.addListener(() => {
// Remove port from all tabs
tabManager.getAllTabs().forEach(tabState => {
tabState.removePort(port);
});
});
});
// Handle all messages
chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => {
// Handle content script messages
if (sender.tab) {
if (request.type === 'contentScriptReady') {
console.log(`Content script ready in tab ${sender.tab.id}`);
// Send current connection state to the newly ready content script
const tabState = tabManager.getTab(sender.tab.id);
if (tabState) {
const connectionState = tabState.getConnectionState();
sendConnectionStateToTab(sender.tab.id, connectionState);
}
sendResponse({ acknowledged: true });
return false;
}
if (request.type === 'connect') {
const result = await tabManager.connect(sender.tab.id);
sendResponse(result);
return true; // Keep message channel open for async response
}
if (request.type === 'disconnect') {
const result = tabManager.disconnect(sender.tab.id);
sendResponse(result);
return false;
}
if (request.type === 'openPopup') {
chrome.action.openPopup();
sendResponse({ ok: true });
return false;
}
if (request.type === 'mousePosition') {
// Store mouse position for the tab
const tabState = tabManager.getTab(sender.tab.id);
if (tabState) {
tabState.setMousePosition({ x: request.x, y: request.y });
}
return false;
}
if (request.type === 'consoleLog') {
// Handle console log from content script
const tabState = tabManager.getTab(sender.tab.id);
if (tabState) {
if (request.level === 'clear') {
// Clear console logs
tabManager.clearConsoleLogs(sender.tab.id);
} else {
// Add console log
tabManager.addConsoleLog(sender.tab.id, new ConsoleLogEntry(
request.level,
request.args,
request.stackTrace
));
}
}
return false;
}
}
// Handle messages from popup/panel (not from content scripts)
if (!sender.tab) {
if (request.type === 'connect' && request.tabId) {
const result = await tabManager.connect(request.tabId);
sendResponse(result);
return true; // Keep message channel open for async response
}
if (request.type === 'disconnect' && request.tabId) {
const result = tabManager.disconnect(request.tabId);
sendResponse(result);
return false;
}
if (request.type === 'getState' && request.tabId) {
const tabState = tabManager.getTab(request.tabId);
sendResponse(tabState ? tabState.getConnectionState() : { connected: false, status: 'disconnected' });
return false;
}
}
});
// Update badge when active tab changes
chrome.tabs.onActivated.addListener((activeInfo) => {
const tabState = tabManager.getTab(activeInfo.tabId);
if (tabState) {
updateActionBadge(tabState.getConnectionState());
} else {
updateActionBadge({ status: 'disconnected' });
}
});
// Clean up when tabs are closed
chrome.tabs.onRemoved.addListener((tabId) => {
tabManager.removeTab(tabId);
});
chrome.runtime.onInstalled.addListener((details) => {
if (details.reason === 'install') {
const destination = { url: 'https://to.kap.co/kapture-welcome' };
// Navigate the current active tab instead of creating a new one
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
if (tabs[0]) {
chrome.tabs.update(tabs[0].id, destination);
}
else {
// Fallback: create a new tab if no active tab is found
chrome.tabs.create(destination);
}
});
}
});