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.
@@ -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-8izT4vax.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};
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};
@@ -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-8izT4vax.js"></script>
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.1",
3
+ "version": "0.22.2",
4
4
  "releaseNotes": [
5
- "1. react router implemented",
6
- "2. new workspace design",
7
- "3. tour",
8
- "4. worspace helth checker"
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
- const payload: any = { conversationId, content };
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
  }
@@ -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-v9';
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 the WebSocket connection to the user's Bloby server
6
- * - Bridges messages between content scripts and the server
7
- * - Handles pairing state (serverUrl stored in chrome.storage.local)
8
- * - Keeps WS alive with heartbeat pings
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', 'authToken']);
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: ws?.readyState === WebSocket.OPEN,
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: send message to server
183
- if (msg.type === 'bloby:send') {
184
- const ok = sendToServer(msg.payload);
185
- sendResponse({ sent: ok });
186
- return false;
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
- // Content script: get page context
190
- if (msg.type === 'bloby:page-context') {
191
- // Content script provides context, we just forward
192
- sendResponse({ received: true });
193
- return false;
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
- // Connect immediately
225
- connectWs();
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) connectWs();
179
+ if (hasCfg && config.serverUrl) {
180
+ await updateFrameRules(config.serverUrl);
181
+ }
239
182
  })();