bloby-bot 0.22.1 → 0.22.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-bloby/assets/{bloby-8izT4vax.js → bloby-DvSkie1b.js} +7 -5
- package/dist-bloby/assets/{highlighted-body-OFNGDK62-C3HbR2K9.js → highlighted-body-OFNGDK62-BbEkUgTM.js} +1 -1
- package/dist-bloby/assets/mermaid-GHXKKRXX-C0VBooMz.js +1 -0
- package/dist-bloby/bloby.html +1 -1
- package/package.json +5 -5
- package/supervisor/chat/src/hooks/useBlobyChat.ts +40 -1
- package/supervisor/index.ts +1 -1
- package/workspace/skills/chrome-extension/SKILL.md +48 -0
- package/workspace/skills/chrome-extension/background.js +77 -134
- package/workspace/skills/chrome-extension/content-script.js +156 -65
- package/workspace/skills/chrome-extension/manifest.json +3 -2
- package/workspace/skills/chrome-extension/panel/panel.html +4 -42
- package/workspace/skills/chrome-extension/panel/panel.js +123 -0
- package/dist-bloby/assets/mermaid-GHXKKRXX-BX4zpcbf.js +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{c as e,r as t,t as n}from"./jsx-runtime-C0W9Wf2W.js";import{n as r,r as i,t as a}from"./bloby-
|
|
1
|
+
import{c as e,r as t,t as n}from"./jsx-runtime-C0W9Wf2W.js";import{n as r,r as i,t as a}from"./bloby-DvSkie1b.js";var o=e(t(),1),s=n(),c=({code:e,language:t,raw:n,className:c,startLine:l,lineNumbers:u,...d})=>{let{shikiTheme:f}=(0,o.useContext)(i),p=r(),[m,h]=(0,o.useState)(n);return(0,o.useEffect)(()=>{if(!p){h(n);return}let r=p.highlight({code:e,language:t,themes:f},e=>{h(e)});r&&h(r)},[e,t,f,p,n]),(0,s.jsx)(a,{className:c,language:t,lineNumbers:u,result:m,startLine:l,...d})};export{c as HighlightedCodeBlockBody};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{i as e}from"./bloby-DvSkie1b.js";export{e as Mermaid};
|
package/dist-bloby/bloby.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, interactive-widget=resizes-content" />
|
|
6
6
|
<title>Bloby Chat</title>
|
|
7
|
-
<script type="module" crossorigin src="/bloby/assets/bloby-
|
|
7
|
+
<script type="module" crossorigin src="/bloby/assets/bloby-DvSkie1b.js"></script>
|
|
8
8
|
<link rel="modulepreload" crossorigin href="/bloby/assets/jsx-runtime-C0W9Wf2W.js">
|
|
9
9
|
<link rel="modulepreload" crossorigin href="/bloby/assets/globals-VdwDxdso.js">
|
|
10
10
|
<link rel="stylesheet" crossorigin href="/bloby/assets/globals-b7xkhPEo.css">
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bloby-bot",
|
|
3
|
-
"version": "0.22.
|
|
3
|
+
"version": "0.22.2",
|
|
4
4
|
"releaseNotes": [
|
|
5
|
-
"1.
|
|
6
|
-
"2.
|
|
7
|
-
"3.
|
|
8
|
-
"4.
|
|
5
|
+
"1. testing chrome extension",
|
|
6
|
+
"2. ",
|
|
7
|
+
"3. ",
|
|
8
|
+
"4. "
|
|
9
9
|
],
|
|
10
10
|
"description": "Self-hosted, self-evolving AI agent with its own dashboard.",
|
|
11
11
|
"type": "module",
|
|
@@ -23,11 +23,33 @@ export function useBlobyChat(ws: WsClient | null, triggerReload?: number, enable
|
|
|
23
23
|
const streamBufferRef = useRef('');
|
|
24
24
|
/** Length of text already committed as a partial message — strip from next bot:response */
|
|
25
25
|
const committedTextLength = useRef(0);
|
|
26
|
+
/** Page context from Chrome extension (URL, title, etc.) */
|
|
27
|
+
const extensionPageContext = useRef<any>(null);
|
|
28
|
+
/** Whether we're running inside the Chrome extension */
|
|
29
|
+
const isExtension = useRef(new URLSearchParams(location.search).has('ext'));
|
|
26
30
|
|
|
27
31
|
// Keep refs in sync with state
|
|
28
32
|
useEffect(() => { conversationIdRef.current = conversationId; }, [conversationId]);
|
|
29
33
|
useEffect(() => { streamingRef.current = streaming; }, [streaming]);
|
|
30
34
|
|
|
35
|
+
// Listen for page context from Chrome extension panel
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (!isExtension.current) return;
|
|
38
|
+
|
|
39
|
+
function handleExtMessage(event: MessageEvent) {
|
|
40
|
+
if (event.data?.type === 'bloby:page-context') {
|
|
41
|
+
extensionPageContext.current = event.data.context;
|
|
42
|
+
console.log('[blobyChat] Extension page context:', event.data.context?.url);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
window.addEventListener('message', handleExtMessage);
|
|
47
|
+
// Request initial context
|
|
48
|
+
window.parent?.postMessage({ type: 'bloby:request-context' }, '*');
|
|
49
|
+
|
|
50
|
+
return () => window.removeEventListener('message', handleExtMessage);
|
|
51
|
+
}, []);
|
|
52
|
+
|
|
31
53
|
// Parse a raw DB message into a ChatMessage
|
|
32
54
|
const parseMessage = useCallback((m: any): ChatMessage => {
|
|
33
55
|
let audioData: string | undefined;
|
|
@@ -317,7 +339,24 @@ export function useBlobyChat(ws: WsClient | null, triggerReload?: number, enable
|
|
|
317
339
|
setMessages((msgs) => [...msgs, userMsg]);
|
|
318
340
|
}
|
|
319
341
|
|
|
320
|
-
|
|
342
|
+
// Prepend page context if running in Chrome extension
|
|
343
|
+
let messageContent = content;
|
|
344
|
+
if (isExtension.current && extensionPageContext.current) {
|
|
345
|
+
const ctx = extensionPageContext.current;
|
|
346
|
+
const parts = [`[Page: ${ctx.url}`];
|
|
347
|
+
if (ctx.title) parts[0] += ` | ${ctx.title}`;
|
|
348
|
+
if (ctx.productName) parts[0] += ` | ${ctx.productName}`;
|
|
349
|
+
if (ctx.price) parts[0] += ` | ${ctx.currency || '$'}${ctx.price}`;
|
|
350
|
+
parts[0] += ']';
|
|
351
|
+
if (ctx.selection) parts.push(`[Selected: ${ctx.selection}]`);
|
|
352
|
+
if (ctx.hasForms) parts.push(`[Page has ${ctx.formCount} form(s)]`);
|
|
353
|
+
messageContent = parts.join('\n') + '\n' + content;
|
|
354
|
+
|
|
355
|
+
// Request fresh context for next message
|
|
356
|
+
window.parent?.postMessage({ type: 'bloby:request-context' }, '*');
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const payload: any = { conversationId, content: messageContent };
|
|
321
360
|
if (audioData) {
|
|
322
361
|
payload.audioData = audioData.includes(',') ? audioData.split(',')[1] : audioData;
|
|
323
362
|
}
|
package/supervisor/index.ts
CHANGED
|
@@ -80,7 +80,7 @@ const SW_JS = `// Service worker — app-shell caching + push notifications
|
|
|
80
80
|
// JS/CSS modules → stale-while-revalidate
|
|
81
81
|
// API, WebSocket, Vite internals → network-only (no cache)
|
|
82
82
|
|
|
83
|
-
var CACHE = 'bloby-
|
|
83
|
+
var CACHE = 'bloby-v10';
|
|
84
84
|
var HASHED_RE = new RegExp('/assets/.+-[a-zA-Z0-9]{6,}[.](js|css)$');
|
|
85
85
|
|
|
86
86
|
// Precache the HTML shell on install so the cache is never empty.
|
|
@@ -101,6 +101,54 @@ curl -s "https://the-url-from-context" | head -200
|
|
|
101
101
|
|
|
102
102
|
Or use an MCP fetch tool if configured.
|
|
103
103
|
|
|
104
|
+
## Site-Specific Behaviors
|
|
105
|
+
|
|
106
|
+
When you detect the user is on a specific type of site (from the page context URL), adapt what you offer:
|
|
107
|
+
|
|
108
|
+
### YouTube (youtube.com, youtu.be)
|
|
109
|
+
- Offer to transcribe the video
|
|
110
|
+
- Offer to summarize, create bullet points, or extract key moments
|
|
111
|
+
- Offer to turn it into a blog post or audio summary
|
|
112
|
+
- The video URL is in the page context — you can fetch it or use tools to process it
|
|
113
|
+
|
|
114
|
+
### Shopping (amazon.com, ebay.com, walmart.com, etsy.com, etc.)
|
|
115
|
+
- Offer to find cheaper alternatives (web search)
|
|
116
|
+
- Offer to save to a wishlist (workspace file)
|
|
117
|
+
- Offer to compare prices across sites
|
|
118
|
+
- Product name and price are extracted from structured data when available
|
|
119
|
+
|
|
120
|
+
### Reddit / X / Social (reddit.com, x.com, twitter.com)
|
|
121
|
+
- Offer to summarize long threads
|
|
122
|
+
- Offer to draft a reply (the extension can type into input fields)
|
|
123
|
+
- Offer to save interesting posts to workspace notes
|
|
124
|
+
|
|
125
|
+
### Documentation / Articles (docs.*, medium.com, dev.to, any long-form text)
|
|
126
|
+
- Offer to summarize the article
|
|
127
|
+
- If text is selected, explain or elaborate on the selection
|
|
128
|
+
- Offer to save key points to workspace notes
|
|
129
|
+
|
|
130
|
+
### Forms (any page with detected form fields)
|
|
131
|
+
- When `hasForms` is true in context, offer to help fill the form
|
|
132
|
+
- Ask what information the user wants to fill
|
|
133
|
+
- Use the form field names from context to understand what's needed
|
|
134
|
+
|
|
135
|
+
### Email (gmail.com, outlook.com)
|
|
136
|
+
- Offer to draft replies
|
|
137
|
+
- Offer to summarize email threads
|
|
138
|
+
|
|
139
|
+
### Blocked Sites — NEVER interact with page content
|
|
140
|
+
- Banking: chase.com, wellsfargo.com, bankofamerica.com, any URL with /banking
|
|
141
|
+
- Auth pages: any URL with /login, /signin, /auth, /password
|
|
142
|
+
- Password managers: 1password.com, bitwarden.com, lastpass.com
|
|
143
|
+
- Medical records: mychart.com, any URL with /health-records
|
|
144
|
+
|
|
145
|
+
On blocked sites, you can still chat normally — just don't read or reference page content. If the user asks about the page, tell them: "I don't interact with sensitive pages like banking or login screens for your security."
|
|
146
|
+
|
|
147
|
+
### Default (any other site)
|
|
148
|
+
- Always share the URL and title (they're in the context)
|
|
149
|
+
- If the user asks about "this page" or "this", fetch the URL for details
|
|
150
|
+
- Offer to save interesting content to workspace
|
|
151
|
+
|
|
104
152
|
## Human Interaction
|
|
105
153
|
|
|
106
154
|
### What to tell your human
|
|
@@ -2,24 +2,21 @@
|
|
|
2
2
|
* Bloby Chrome Extension — Background Service Worker
|
|
3
3
|
*
|
|
4
4
|
* Responsibilities:
|
|
5
|
-
* - Manages
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
8
|
-
*
|
|
5
|
+
* - Manages pairing state (serverUrl stored in chrome.storage.local)
|
|
6
|
+
* - Handles pairing flow (verify code → save config)
|
|
7
|
+
* - Responds to state queries from content scripts and popup
|
|
8
|
+
*
|
|
9
|
+
* Note: WebSocket connection is handled by the chat iframe inside the panel,
|
|
10
|
+
* not by the background worker. The iframe loads the Bloby chat app which
|
|
11
|
+
* manages its own WS connection and auth (portal login).
|
|
9
12
|
*/
|
|
10
13
|
|
|
11
|
-
let ws = null;
|
|
12
14
|
let config = null; // { serverUrl, username, tier }
|
|
13
|
-
let reconnectTimer = null;
|
|
14
|
-
let reconnectDelay = 1000;
|
|
15
|
-
const MAX_RECONNECT_DELAY = 8000;
|
|
16
|
-
const HEARTBEAT_MS = 25000;
|
|
17
|
-
let heartbeatTimer = null;
|
|
18
15
|
|
|
19
16
|
// ── Config Management ──────────────────────────────────────────────────────
|
|
20
17
|
|
|
21
18
|
async function loadConfig() {
|
|
22
|
-
const data = await chrome.storage.local.get(['serverUrl', 'username', 'tier'
|
|
19
|
+
const data = await chrome.storage.local.get(['serverUrl', 'username', 'tier']);
|
|
23
20
|
if (data.serverUrl) {
|
|
24
21
|
config = data;
|
|
25
22
|
console.log(`[bloby-bg] Config loaded: ${config.serverUrl} (${config.username})`);
|
|
@@ -38,107 +35,9 @@ async function saveConfig(newConfig) {
|
|
|
38
35
|
async function clearConfig() {
|
|
39
36
|
config = null;
|
|
40
37
|
await chrome.storage.local.clear();
|
|
41
|
-
disconnectWs();
|
|
42
38
|
console.log('[bloby-bg] Config cleared — unpaired');
|
|
43
39
|
}
|
|
44
40
|
|
|
45
|
-
// ── WebSocket Connection ───────────────────────────────────────────────────
|
|
46
|
-
|
|
47
|
-
function connectWs() {
|
|
48
|
-
if (!config?.serverUrl) return;
|
|
49
|
-
if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) return;
|
|
50
|
-
|
|
51
|
-
const proto = config.serverUrl.startsWith('https') ? 'wss' : 'ws';
|
|
52
|
-
const host = config.serverUrl.replace(/^https?:\/\//, '');
|
|
53
|
-
let url = `${proto}://${host}/bloby/ws`;
|
|
54
|
-
if (config.authToken) {
|
|
55
|
-
url += `?token=${config.authToken}`;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
console.log(`[bloby-bg] Connecting WS: ${url.replace(/token=.*/, 'token=***')}`);
|
|
59
|
-
|
|
60
|
-
try {
|
|
61
|
-
ws = new WebSocket(url);
|
|
62
|
-
} catch (err) {
|
|
63
|
-
console.error('[bloby-bg] WS constructor error:', err);
|
|
64
|
-
scheduleReconnect();
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
ws.onopen = () => {
|
|
69
|
-
console.log('[bloby-bg] WS connected');
|
|
70
|
-
reconnectDelay = 1000;
|
|
71
|
-
startHeartbeat();
|
|
72
|
-
// Notify all tabs
|
|
73
|
-
broadcastToTabs({ type: 'bloby:ws-connected' });
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
ws.onmessage = (event) => {
|
|
77
|
-
try {
|
|
78
|
-
const msg = JSON.parse(event.data);
|
|
79
|
-
// Forward all server messages to content scripts
|
|
80
|
-
broadcastToTabs(msg);
|
|
81
|
-
} catch { /* ignore non-JSON */ }
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
ws.onclose = (event) => {
|
|
85
|
-
console.log(`[bloby-bg] WS closed: code=${event.code} reason=${event.reason}`);
|
|
86
|
-
stopHeartbeat();
|
|
87
|
-
ws = null;
|
|
88
|
-
broadcastToTabs({ type: 'bloby:ws-disconnected' });
|
|
89
|
-
scheduleReconnect();
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
ws.onerror = (err) => {
|
|
93
|
-
console.error('[bloby-bg] WS error');
|
|
94
|
-
// onclose will fire after this
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function disconnectWs() {
|
|
99
|
-
stopHeartbeat();
|
|
100
|
-
if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; }
|
|
101
|
-
if (ws) {
|
|
102
|
-
ws.onclose = null; // prevent reconnect
|
|
103
|
-
ws.close();
|
|
104
|
-
ws = null;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function scheduleReconnect() {
|
|
109
|
-
if (reconnectTimer) return;
|
|
110
|
-
console.log(`[bloby-bg] Reconnecting in ${reconnectDelay}ms...`);
|
|
111
|
-
reconnectTimer = setTimeout(() => {
|
|
112
|
-
reconnectTimer = null;
|
|
113
|
-
reconnectDelay = Math.min(reconnectDelay * 2, MAX_RECONNECT_DELAY);
|
|
114
|
-
connectWs();
|
|
115
|
-
}, reconnectDelay);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function startHeartbeat() {
|
|
119
|
-
stopHeartbeat();
|
|
120
|
-
heartbeatTimer = setInterval(() => {
|
|
121
|
-
if (ws?.readyState === WebSocket.OPEN) {
|
|
122
|
-
ws.send(JSON.stringify({ type: 'ping' }));
|
|
123
|
-
}
|
|
124
|
-
}, HEARTBEAT_MS);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function stopHeartbeat() {
|
|
128
|
-
if (heartbeatTimer) { clearInterval(heartbeatTimer); heartbeatTimer = null; }
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// ── Send to Server ─────────────────────────────────────────────────────────
|
|
132
|
-
|
|
133
|
-
function sendToServer(msg) {
|
|
134
|
-
if (ws?.readyState === WebSocket.OPEN) {
|
|
135
|
-
ws.send(JSON.stringify(msg));
|
|
136
|
-
return true;
|
|
137
|
-
}
|
|
138
|
-
console.warn('[bloby-bg] WS not connected — message dropped');
|
|
139
|
-
return false;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
41
|
// ── Broadcast to All Tabs ──────────────────────────────────────────────────
|
|
143
42
|
|
|
144
43
|
async function broadcastToTabs(msg) {
|
|
@@ -146,9 +45,7 @@ async function broadcastToTabs(msg) {
|
|
|
146
45
|
const tabs = await chrome.tabs.query({});
|
|
147
46
|
for (const tab of tabs) {
|
|
148
47
|
if (tab.id) {
|
|
149
|
-
chrome.tabs.sendMessage(tab.id, msg).catch(() => {
|
|
150
|
-
// Tab doesn't have content script — ignore
|
|
151
|
-
});
|
|
48
|
+
chrome.tabs.sendMessage(tab.id, msg).catch(() => {});
|
|
152
49
|
}
|
|
153
50
|
}
|
|
154
51
|
} catch { /* ignore */ }
|
|
@@ -157,40 +54,42 @@ async function broadcastToTabs(msg) {
|
|
|
157
54
|
// ── Message Handler (from content scripts + popup) ─────────────────────────
|
|
158
55
|
|
|
159
56
|
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
|
160
|
-
// Popup: get current state
|
|
161
57
|
if (msg.type === 'bloby:get-state') {
|
|
162
58
|
sendResponse({
|
|
163
59
|
paired: !!config?.serverUrl,
|
|
164
|
-
connected:
|
|
60
|
+
connected: true, // iframe manages its own connection
|
|
165
61
|
config: config ? { serverUrl: config.serverUrl, username: config.username } : null,
|
|
166
62
|
});
|
|
167
63
|
return true;
|
|
168
64
|
}
|
|
169
65
|
|
|
170
|
-
// Popup: pair with code
|
|
171
66
|
if (msg.type === 'bloby:pair') {
|
|
172
67
|
handlePair(msg.code).then(sendResponse);
|
|
173
|
-
return true; // async response
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Popup: unpair
|
|
177
|
-
if (msg.type === 'bloby:unpair') {
|
|
178
|
-
clearConfig().then(() => sendResponse({ success: true }));
|
|
179
68
|
return true;
|
|
180
69
|
}
|
|
181
70
|
|
|
182
|
-
// Content script:
|
|
183
|
-
if (msg.type === 'bloby:
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
71
|
+
// Content script: take screenshot
|
|
72
|
+
if (msg.type === 'bloby:screenshot') {
|
|
73
|
+
chrome.tabs.captureVisibleTab(null, { format: 'png' }, (dataUrl) => {
|
|
74
|
+
sendResponse({ dataUrl: dataUrl || null });
|
|
75
|
+
});
|
|
76
|
+
return true; // async
|
|
187
77
|
}
|
|
188
78
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
79
|
+
if (msg.type === 'bloby:unpair') {
|
|
80
|
+
(async () => {
|
|
81
|
+
// Clear frame rules
|
|
82
|
+
try {
|
|
83
|
+
const existing = await chrome.declarativeNetRequest.getDynamicRules();
|
|
84
|
+
await chrome.declarativeNetRequest.updateDynamicRules({
|
|
85
|
+
removeRuleIds: existing.map((r) => r.id),
|
|
86
|
+
});
|
|
87
|
+
} catch {}
|
|
88
|
+
await clearConfig();
|
|
89
|
+
broadcastToTabs({ type: 'bloby:unpaired' });
|
|
90
|
+
sendResponse({ success: true });
|
|
91
|
+
})();
|
|
92
|
+
return true;
|
|
194
93
|
}
|
|
195
94
|
});
|
|
196
95
|
|
|
@@ -221,8 +120,11 @@ async function handlePair(code) {
|
|
|
221
120
|
tier: data.tier,
|
|
222
121
|
});
|
|
223
122
|
|
|
224
|
-
//
|
|
225
|
-
|
|
123
|
+
// Set up header rules to allow iframing the Bloby server
|
|
124
|
+
await updateFrameRules(data.serverUrl);
|
|
125
|
+
|
|
126
|
+
// Notify all tabs to show the bubble
|
|
127
|
+
broadcastToTabs({ type: 'bloby:paired', serverUrl: data.serverUrl });
|
|
226
128
|
|
|
227
129
|
return { success: true, username: data.username, serverUrl: data.serverUrl };
|
|
228
130
|
} catch (err) {
|
|
@@ -231,9 +133,50 @@ async function handlePair(code) {
|
|
|
231
133
|
}
|
|
232
134
|
}
|
|
233
135
|
|
|
136
|
+
// ── Frame Header Rules ─────────────────────────────────────────────────────
|
|
137
|
+
// Strip X-Frame-Options and restrictive frame-ancestors from the Bloby server
|
|
138
|
+
// so the chat can be loaded in the extension's panel iframe.
|
|
139
|
+
|
|
140
|
+
async function updateFrameRules(serverUrl) {
|
|
141
|
+
const host = serverUrl.replace(/^https?:\/\//, '').replace(/\/$/, '');
|
|
142
|
+
console.log(`[bloby-bg] Setting frame rules for: *://${host}/*`);
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
// Remove old rules
|
|
146
|
+
const existing = await chrome.declarativeNetRequest.getDynamicRules();
|
|
147
|
+
const removeIds = existing.map((r) => r.id);
|
|
148
|
+
|
|
149
|
+
await chrome.declarativeNetRequest.updateDynamicRules({
|
|
150
|
+
removeRuleIds: removeIds,
|
|
151
|
+
addRules: [
|
|
152
|
+
{
|
|
153
|
+
id: 1,
|
|
154
|
+
priority: 1,
|
|
155
|
+
action: {
|
|
156
|
+
type: 'modifyHeaders',
|
|
157
|
+
responseHeaders: [
|
|
158
|
+
{ header: 'X-Frame-Options', operation: 'remove' },
|
|
159
|
+
{ header: 'Content-Security-Policy', operation: 'remove' },
|
|
160
|
+
],
|
|
161
|
+
},
|
|
162
|
+
condition: {
|
|
163
|
+
urlFilter: `*://${host}/*`,
|
|
164
|
+
resourceTypes: ['sub_frame'],
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
});
|
|
169
|
+
console.log('[bloby-bg] Frame rules set successfully');
|
|
170
|
+
} catch (err) {
|
|
171
|
+
console.error('[bloby-bg] Failed to set frame rules:', err);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
234
175
|
// ── Startup ────────────────────────────────────────────────────────────────
|
|
235
176
|
|
|
236
177
|
(async () => {
|
|
237
178
|
const hasCfg = await loadConfig();
|
|
238
|
-
if (hasCfg)
|
|
179
|
+
if (hasCfg && config.serverUrl) {
|
|
180
|
+
await updateFrameRules(config.serverUrl);
|
|
181
|
+
}
|
|
239
182
|
})();
|