Local Options Enabled
This commit is contained in:
+116
@@ -0,0 +1,116 @@
|
|||||||
|
// src/background.ts
|
||||||
|
chrome.action.onClicked.addListener((tab) => {
|
||||||
|
const tabId = tab?.id;
|
||||||
|
if (tabId && chrome.sidePanel.open) {
|
||||||
|
chrome.sidePanel.open({ tabId });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log("[Background] onUserScriptMessage available:", !!chrome.runtime.onUserScriptMessage);
|
||||||
|
if (chrome.runtime.onUserScriptMessage) {
|
||||||
|
chrome.runtime.onUserScriptMessage.addListener((message, sender, sendResponse) => {
|
||||||
|
console.log("[Background] Received userScript message:", message, "from:", sender);
|
||||||
|
if (message.type === "abort-repl") {
|
||||||
|
console.log("[Background] Relaying abort-repl to sidepanels");
|
||||||
|
chrome.runtime.sendMessage(message);
|
||||||
|
sendResponse({ success: true });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log("[Background] onUserScriptMessage listener registered");
|
||||||
|
} else {
|
||||||
|
console.error("[Background] onUserScriptMessage NOT available!");
|
||||||
|
}
|
||||||
|
var SIDEPANEL_OPEN_KEY = "sidepanel_open_windows";
|
||||||
|
var SESSION_LOCKS_KEY = "session_locks";
|
||||||
|
var openSidepanels = /* @__PURE__ */ new Set();
|
||||||
|
chrome.storage.session.get(SIDEPANEL_OPEN_KEY, (data) => {
|
||||||
|
openSidepanels = new Set(data[SIDEPANEL_OPEN_KEY] || []);
|
||||||
|
console.log("[Background] Initialized openSidepanels cache:", Array.from(openSidepanels));
|
||||||
|
});
|
||||||
|
chrome.runtime.onConnect.addListener((port) => {
|
||||||
|
const match = /^sidepanel:(\d+)$/.exec(port.name);
|
||||||
|
if (!match) return;
|
||||||
|
const windowId = Number(match[1]);
|
||||||
|
openSidepanels.add(windowId);
|
||||||
|
chrome.storage.session.get(SIDEPANEL_OPEN_KEY, (data) => {
|
||||||
|
const openWindows = new Set(data[SIDEPANEL_OPEN_KEY] || []);
|
||||||
|
openWindows.add(windowId);
|
||||||
|
chrome.storage.session.set({ [SIDEPANEL_OPEN_KEY]: Array.from(openWindows) });
|
||||||
|
});
|
||||||
|
port.onMessage.addListener((msg) => {
|
||||||
|
if (msg.type === "acquireLock") {
|
||||||
|
const { sessionId, windowId: reqWindowId } = msg;
|
||||||
|
chrome.storage.session.get(SESSION_LOCKS_KEY, (data) => {
|
||||||
|
const sessionLocks = data[SESSION_LOCKS_KEY] || {};
|
||||||
|
const ownerWindowId = sessionLocks[sessionId];
|
||||||
|
const ownerSidepanelOpen = ownerWindowId !== void 0 && openSidepanels.has(ownerWindowId);
|
||||||
|
const success = !ownerWindowId || !ownerSidepanelOpen || ownerWindowId === reqWindowId;
|
||||||
|
const response = success ? {
|
||||||
|
type: "lockResult",
|
||||||
|
sessionId,
|
||||||
|
success: true
|
||||||
|
} : {
|
||||||
|
type: "lockResult",
|
||||||
|
sessionId,
|
||||||
|
success: false,
|
||||||
|
ownerWindowId
|
||||||
|
};
|
||||||
|
if (success) {
|
||||||
|
sessionLocks[sessionId] = reqWindowId;
|
||||||
|
chrome.storage.session.set({ [SESSION_LOCKS_KEY]: sessionLocks });
|
||||||
|
}
|
||||||
|
port.postMessage(response);
|
||||||
|
});
|
||||||
|
} else if (msg.type === "getLockedSessions") {
|
||||||
|
chrome.storage.session.get(SESSION_LOCKS_KEY, (data) => {
|
||||||
|
const locks = data[SESSION_LOCKS_KEY] || {};
|
||||||
|
const response = {
|
||||||
|
type: "lockedSessions",
|
||||||
|
locks
|
||||||
|
};
|
||||||
|
port.postMessage(response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
port.onDisconnect.addListener(() => {
|
||||||
|
closeSidepanel(windowId, false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
chrome.windows.onRemoved.addListener((windowId) => {
|
||||||
|
closeSidepanel(windowId, false);
|
||||||
|
});
|
||||||
|
chrome.commands.onCommand.addListener((command, sender) => {
|
||||||
|
if (command === "toggle-sidepanel") {
|
||||||
|
if (!sender?.windowId) {
|
||||||
|
console.log("[Background] Cannot toggle sidepanel: sender windowId not available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const windowId = sender.windowId;
|
||||||
|
if (openSidepanels.has(windowId)) {
|
||||||
|
closeSidepanel(windowId);
|
||||||
|
} else {
|
||||||
|
chrome.sidePanel.open({ windowId });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
function closeSidepanel(windowId, callCloseOnSidePanelAPI = true) {
|
||||||
|
if (callCloseOnSidePanelAPI) {
|
||||||
|
chrome.sidePanel.close({ windowId });
|
||||||
|
}
|
||||||
|
openSidepanels.delete(windowId);
|
||||||
|
chrome.storage.session.get([SESSION_LOCKS_KEY, SIDEPANEL_OPEN_KEY], (data) => {
|
||||||
|
const sessionLocks = data[SESSION_LOCKS_KEY] || {};
|
||||||
|
for (const sessionId in sessionLocks) {
|
||||||
|
if (sessionLocks[sessionId] === windowId) {
|
||||||
|
delete sessionLocks[sessionId];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const openWindows = new Set(data[SIDEPANEL_OPEN_KEY] || []);
|
||||||
|
openWindows.delete(windowId);
|
||||||
|
chrome.storage.session.set({
|
||||||
|
[SESSION_LOCKS_KEY]: sessionLocks,
|
||||||
|
[SIDEPANEL_OPEN_KEY]: Array.from(openWindows)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=background.js.map
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en" class="h-full">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Debug</title>
|
||||||
|
<script src="theme-loader.js"></script>
|
||||||
|
<link rel="stylesheet" href="app.css" />
|
||||||
|
</head>
|
||||||
|
<body class="h-full w-full m-0 overflow-hidden bg-background">
|
||||||
|
<script type="module" src="debug.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 757 B |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
+14
@@ -0,0 +1,14 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Sitegeist Icon Generator</title>
|
||||||
|
<link rel="stylesheet" href="app.css" />
|
||||||
|
<script src="theme-loader.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="icons.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"manifest_version": 3,
|
||||||
|
"minimum_chrome_version": "141",
|
||||||
|
"name": "sitegeist",
|
||||||
|
"description": "Your AI companion for the web - Research, automate, create",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"action": {
|
||||||
|
"default_title": "Click to open side panel"
|
||||||
|
},
|
||||||
|
"background": {
|
||||||
|
"service_worker": "background.js",
|
||||||
|
"type": "module"
|
||||||
|
},
|
||||||
|
"icons": {
|
||||||
|
"16": "icon-16.png",
|
||||||
|
"48": "icon-48.png",
|
||||||
|
"128": "icon-128.png"
|
||||||
|
},
|
||||||
|
"side_panel": {
|
||||||
|
"default_path": "sidepanel.html"
|
||||||
|
},
|
||||||
|
"commands": {
|
||||||
|
"toggle-sidepanel": {
|
||||||
|
"suggested_key": {
|
||||||
|
"default": "Ctrl+Shift+S",
|
||||||
|
"mac": "Command+Shift+S"
|
||||||
|
},
|
||||||
|
"description": "Toggle side panel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"permissions": [
|
||||||
|
"storage",
|
||||||
|
"unlimitedStorage",
|
||||||
|
"activeTab",
|
||||||
|
"scripting",
|
||||||
|
"sidePanel",
|
||||||
|
"userScripts",
|
||||||
|
"webNavigation",
|
||||||
|
"debugger"
|
||||||
|
],
|
||||||
|
"host_permissions": [
|
||||||
|
"http://*/*",
|
||||||
|
"https://*/*",
|
||||||
|
"http://localhost/*",
|
||||||
|
"http://127.0.0.1/*"
|
||||||
|
],
|
||||||
|
"sandbox": {
|
||||||
|
"pages": ["sandbox.html"]
|
||||||
|
},
|
||||||
|
"content_security_policy": {
|
||||||
|
"extension_pages": "script-src 'self'; object-src 'self'",
|
||||||
|
"sandbox": "sandbox allow-scripts allow-modals; default-src 'none'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net/ https://unpkg.com/ https://cdnjs.cloudflare.com/ https://esm.sh/ https://esm.run/ https://cdn.skypack.dev/ https://cdn.tailwindcss.com; script-src-elem 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net/ https://unpkg.com/ https://cdnjs.cloudflare.com/ https://esm.sh/ https://esm.run/ https://cdn.skypack.dev/ https://cdn.tailwindcss.com; connect-src https://cdn.jsdelivr.net/ https://unpkg.com/ https://cdnjs.cloudflare.com/ https://esm.sh/ https://esm.run/ https://cdn.skypack.dev/ https://cdn.tailwindcss.com https://t1.gstatic.com/; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net/ https://cdnjs.cloudflare.com/ https://cdn.tailwindcss.com; img-src 'self' data: blob: https:; font-src 'self' data: https://cdn.jsdelivr.net/ https://cdnjs.cloudflare.com/; object-src 'none'; base-uri 'none'; form-action 'none'"
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Sandboxed Content</title>
|
||||||
|
<style>
|
||||||
|
html { height: 100%; }
|
||||||
|
body { min-height: 100%; margin: 0; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script src="sandbox.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+57
@@ -0,0 +1,57 @@
|
|||||||
|
// Minimal sandbox.js - just listens for sandbox-load and writes the content
|
||||||
|
window.addEventListener("message", (event) => {
|
||||||
|
if (event.data.type === "sandbox-load") {
|
||||||
|
// Validate HTML and JavaScript syntax before document.write()
|
||||||
|
try {
|
||||||
|
// Parse HTML to extract script tags
|
||||||
|
const parser = new DOMParser();
|
||||||
|
const doc = parser.parseFromString(event.data.code, "text/html");
|
||||||
|
|
||||||
|
// Check for HTML parser errors
|
||||||
|
const parserError = doc.querySelector("parsererror");
|
||||||
|
if (parserError) {
|
||||||
|
throw new Error(`HTML parse error: ${parserError.textContent}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate JavaScript in all script tags (except type="module")
|
||||||
|
const scriptTags = Array.from(doc.querySelectorAll("script"));
|
||||||
|
for (let i = 0; i < scriptTags.length; i++) {
|
||||||
|
const scriptContent = scriptTags[i].textContent || "";
|
||||||
|
const scriptType = scriptTags[i].getAttribute("type");
|
||||||
|
|
||||||
|
// Skip validation for module scripts - new Function() can't validate module syntax
|
||||||
|
if (scriptType === "module") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scriptContent.trim()) {
|
||||||
|
try {
|
||||||
|
// Use Function constructor to validate syntax without executing
|
||||||
|
new Function(scriptContent);
|
||||||
|
} catch (jsError) {
|
||||||
|
throw new Error(`JavaScript syntax error in <script> tag ${i + 1}: ${jsError.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (validationError) {
|
||||||
|
// Send validation error back to parent
|
||||||
|
window.parent.postMessage(
|
||||||
|
{
|
||||||
|
type: "sandbox-error",
|
||||||
|
error: validationError.message || String(validationError),
|
||||||
|
stack: validationError.stack || "",
|
||||||
|
},
|
||||||
|
"*"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the complete HTML (which includes runtime + user code)
|
||||||
|
document.open();
|
||||||
|
document.write(event.data.code);
|
||||||
|
document.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Signal ready to parent
|
||||||
|
window.parent.postMessage({ type: "sandbox-ready" }, "*");
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" class="h-full">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>pi-ai</title>
|
||||||
|
<script src="theme-loader.js"></script>
|
||||||
|
<link rel="stylesheet" href="app.css" />
|
||||||
|
</head>
|
||||||
|
<body class="h-full w-full m-0 overflow-hidden bg-background">
|
||||||
|
<script type="module" src="sidepanel.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+335629
File diff suppressed because one or more lines are too long
@@ -0,0 +1,24 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Test Sidebar</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: system-ui;
|
||||||
|
padding: 20px;
|
||||||
|
background: #f0f0f0;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Test Sidebar</h1>
|
||||||
|
<p>If you can see this, the Firefox sidebar is working!</p>
|
||||||
|
<script>
|
||||||
|
console.log("Test sidebar loaded");
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// Apply theme immediately to prevent white flash
|
||||||
|
// This runs before any other scripts or CSS
|
||||||
|
(function() {
|
||||||
|
const theme = localStorage.getItem('theme') || 'system';
|
||||||
|
if (theme === 'dark' || (theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||||
|
document.documentElement.classList.add('dark');
|
||||||
|
}
|
||||||
|
})();
|
||||||
Reference in New Issue
Block a user