Skip to content

Commit 320249b

Browse files
Merge pull request #12 from BitcoinErrorLog/fix/phase3-medium-priority-fixes
fix: Phase 3 Medium Priority Fixes - Memory Leaks, Types, Logging, Testnet
2 parents ad07907 + bd9f4ca commit 320249b

File tree

10 files changed

+285
-45
lines changed

10 files changed

+285
-45
lines changed

manifest.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,14 @@
7777
},
7878
"content_security_policy": {
7979
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'"
80+
// Note: 'wasm-unsafe-eval' is required for Pubky SDK WebAssembly support
8081
},
8182
"web_accessible_resources": [
8283
{
8384
"resources": ["src/profile/profile-renderer.html"],
8485
"matches": ["<all_urls>"]
86+
// Note: Profile renderer needs to be accessible from any page to display
87+
// pubky:// URLs. This is a core feature requirement.
8588
}
8689
]
8790
}

src/background/background.ts

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,42 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
6464
sendResponse({ success: true });
6565
}
6666

67+
if (message.type === 'OPEN_SIDE_PANEL_FOR_ANNOTATION') {
68+
// Open sidepanel in response to annotation click (user gesture preserved)
69+
// Must call sidePanel.open() immediately to preserve user gesture context
70+
const tabId = sender.tab?.id;
71+
if (tabId) {
72+
chrome.sidePanel.open({ tabId }, () => {
73+
if (chrome.runtime.lastError) {
74+
logger.error('Background', 'Failed to open sidepanel', new Error(chrome.runtime.lastError.message));
75+
sendResponse({ success: false, error: chrome.runtime.lastError.message });
76+
} else {
77+
logger.info('Background', 'Sidepanel opened for annotation', {
78+
annotationId: message.annotationId,
79+
tabId
80+
});
81+
82+
// Send scroll message after a short delay to allow sidepanel to load
83+
setTimeout(() => {
84+
chrome.runtime.sendMessage({
85+
type: 'SCROLL_TO_ANNOTATION',
86+
annotationId: message.annotationId,
87+
}).catch(() => {
88+
// Sidepanel might not be ready yet, that's okay
89+
logger.debug('Background', 'Sidepanel not ready for scroll message yet');
90+
});
91+
}, 300);
92+
93+
sendResponse({ success: true });
94+
}
95+
});
96+
} else {
97+
logger.warn('Background', 'No tab ID available for opening sidepanel');
98+
sendResponse({ success: false, error: 'No tab ID available' });
99+
}
100+
return true; // Keep message channel open for async response
101+
}
102+
67103
if (message.type === MESSAGE_TYPES.CREATE_ANNOTATION) {
68104
// Handle annotation creation
69105
handleCreateAnnotation(message.annotation)
@@ -643,54 +679,47 @@ chrome.webNavigation.onBeforeNavigate.addListener((details) => {
643679

644680
// Handle keyboard commands
645681
// NOTE: Must NOT use async/await here to preserve user gesture context
646-
console.log('[Graphiti] Registering keyboard command listener');
682+
logger.info('Background', 'Registering keyboard command listener');
647683
chrome.commands.onCommand.addListener((command) => {
648-
// Use console.log directly for immediate visibility in service worker console
649-
console.log('[Graphiti] Command received:', command);
650684
logger.info('Background', 'Command received', { command });
651685

652686
if (command === COMMAND_NAMES.TOGGLE_SIDEPANEL) {
653-
console.log('[Graphiti] toggle-sidepanel command triggered');
687+
logger.info('Background', 'toggle-sidepanel command triggered');
654688
// Open the side panel - must call open() immediately to preserve user gesture
655689
// Using windowId instead of tabId since it's available synchronously
656690
chrome.windows.getCurrent((window) => {
657691
if (!window?.id) {
658-
console.warn('[Graphiti] No current window found');
659692
logger.warn('Background', 'No current window found for side panel toggle');
660693
return;
661694
}
662695

663-
console.log('[Graphiti] Opening side panel for window:', window.id);
696+
logger.info('Background', 'Opening side panel', { windowId: window.id });
664697
chrome.sidePanel.open({ windowId: window.id }, () => {
665698
if (chrome.runtime.lastError) {
666-
console.error('[Graphiti] Failed to open:', chrome.runtime.lastError.message);
667699
logger.error('Background', 'Failed to open side panel', new Error(chrome.runtime.lastError.message));
668700
} else {
669-
console.log('[Graphiti] Side panel opened successfully');
670701
logger.info('Background', 'Side panel opened via keyboard shortcut', { windowId: window.id });
671702
}
672703
});
673704
});
674705
}
675706

676707
if (command === COMMAND_NAMES.OPEN_ANNOTATIONS) {
677-
console.log('[Graphiti] open-annotations command triggered');
708+
logger.info('Background', 'open-annotations command triggered');
678709
// Open side panel and switch to annotations tab
679710
// Must call open() immediately to preserve user gesture
680711
chrome.windows.getCurrent((window) => {
681712
if (!window?.id) {
682-
console.warn('[Graphiti] No current window found');
683713
logger.warn('Background', 'No current window found for annotations');
684714
return;
685715
}
686716

687-
console.log('[Graphiti] Opening side panel for annotations, window:', window.id);
717+
logger.info('Background', 'Opening side panel for annotations', { windowId: window.id });
688718
chrome.sidePanel.open({ windowId: window.id }, () => {
689719
if (chrome.runtime.lastError) {
690-
console.error('[Graphiti] Failed to open:', chrome.runtime.lastError.message);
691720
logger.error('Background', 'Failed to open annotations', new Error(chrome.runtime.lastError.message));
692721
} else {
693-
console.log('[Graphiti] Side panel opened, switching to annotations tab...');
722+
logger.info('Background', 'Side panel opened, switching to annotations tab');
694723
// Send message to sidebar to switch to annotations tab
695724
setTimeout(() => {
696725
chrome.runtime.sendMessage({
@@ -709,19 +738,18 @@ chrome.commands.onCommand.addListener((command) => {
709738
}
710739

711740
if (command === COMMAND_NAMES.TOGGLE_DRAWING) {
712-
console.log('[Graphiti] toggle-drawing command triggered');
741+
logger.info('Background', 'toggle-drawing command triggered');
713742
// Toggle drawing mode on the current tab
714743
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
715744
const tab = tabs[0];
716-
console.log('[Graphiti] Active tab for drawing:', tab?.id, tab?.url);
745+
logger.info('Background', 'Active tab for drawing', { tabId: tab?.id, url: tab?.url });
717746

718747
if (tab?.id && tab.url && !tab.url.startsWith('chrome://') && !tab.url.startsWith('about:') && !tab.url.startsWith('chrome-extension://')) {
719-
console.log('[Graphiti] Sending TOGGLE_DRAWING_MODE to tab', tab.id);
748+
logger.info('Background', 'Sending TOGGLE_DRAWING_MODE to tab', { tabId: tab.id });
720749
chrome.tabs.sendMessage(tab.id, {
721750
type: 'TOGGLE_DRAWING_MODE',
722751
}, (response) => {
723752
if (chrome.runtime.lastError) {
724-
console.error('[Graphiti] Drawing mode error:', chrome.runtime.lastError.message);
725753
logger.error('Background', 'Failed to toggle drawing mode - content script may not be ready', new Error(chrome.runtime.lastError.message));
726754
// Try to notify user
727755
chrome.notifications?.create({
@@ -731,15 +759,13 @@ chrome.commands.onCommand.addListener((command) => {
731759
message: 'Please refresh the page to use drawing mode on this site.'
732760
});
733761
} else {
734-
console.log('[Graphiti] Drawing mode toggled successfully:', response?.active);
735762
logger.info('Background', 'Drawing mode toggled via keyboard shortcut', {
736763
tabId: tab.id,
737764
active: response?.active
738765
});
739766
}
740767
});
741768
} else {
742-
console.warn('[Graphiti] Cannot use drawing mode on this page:', tab?.url);
743769
logger.warn('Background', 'Cannot use drawing mode on this page', { url: tab?.url });
744770
chrome.notifications?.create({
745771
type: 'basic',
@@ -752,7 +778,7 @@ chrome.commands.onCommand.addListener((command) => {
752778
}
753779
});
754780

755-
console.log('[Graphiti] Background script command listeners registered');
781+
logger.info('Background', 'Command listeners registered');
756782

757783
// Handle errors
758784
self.addEventListener('error', (event) => {

src/config/config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ const DEFAULT_CONFIG: AppConfig = {
4646
function loadConfig(): AppConfig {
4747
// Vite exposes env variables via import.meta.env
4848
// In Vite projects, import.meta.env is always available at build time
49-
// @ts-ignore - import.meta is a Vite feature
49+
// @ts-ignore - import.meta is a Vite feature (not in standard TypeScript lib)
50+
// This is safe as Vite transforms this at build time
5051
const viteEnv = (globalThis as any).import?.meta?.env ||
5152
(typeof window !== 'undefined' && (window as any).__VITE_ENV__) ||
5253
{};

src/content/AnnotationManager.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { contentLogger as logger } from './logger';
2-
// @ts-ignore - No type definitions available
2+
// @ts-ignore - dom-anchor-text-quote lacks TypeScript definitions
3+
// Tracking: https://github.com/nicksellen/dom-anchor-text-quote
4+
// TODO: Create PR with type definitions or find alternative library
35
import * as textQuote from 'dom-anchor-text-quote';
46
import {
57
validateSelectedText,

src/content/PubkyURLHandler.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { contentLogger as logger } from './logger';
22
import DOMPurify from 'dompurify';
33

44
export class PubkyURLHandler {
5+
private domObserver: MutationObserver | null = null;
6+
57
constructor() {
68
this.init();
79
}
@@ -202,7 +204,12 @@ export class PubkyURLHandler {
202204
private observeDOMForPubkyURLs() {
203205
let isProcessing = false;
204206

205-
const observer = new MutationObserver((mutations) => {
207+
// Disconnect existing observer if any
208+
if (this.domObserver) {
209+
this.domObserver.disconnect();
210+
}
211+
212+
this.domObserver = new MutationObserver((mutations) => {
206213
const isOurMutation = mutations.some(mutation => {
207214
return Array.from(mutation.addedNodes).some(node => {
208215
if (node.nodeType === Node.ELEMENT_NODE) {
@@ -232,10 +239,22 @@ export class PubkyURLHandler {
232239
}
233240
});
234241

235-
observer.observe(document.body, {
242+
this.domObserver.observe(document.body, {
236243
childList: true,
237244
subtree: true,
238245
});
239246
}
247+
248+
/**
249+
* Cleanup method to disconnect observer and remove event listeners
250+
* Should be called when the handler is no longer needed
251+
*/
252+
cleanup(): void {
253+
if (this.domObserver) {
254+
this.domObserver.disconnect();
255+
this.domObserver = null;
256+
}
257+
document.removeEventListener('click', this.handleClick, true);
258+
}
240259
}
241260

src/offscreen/offscreen.ts

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
*/
1212

1313
import { storage } from '../utils/storage';
14+
import { logger } from '../utils/logger';
1415

1516
// Import Pubky SDK types
1617
type Client = any;
@@ -52,18 +53,18 @@ class OffscreenHandler {
5253
*/
5354
private async initialize(): Promise<void> {
5455
try {
55-
console.log('[Graphiti Offscreen] Initializing Pubky client...');
56+
logger.info('Offscreen', 'Initializing Pubky client');
5657

5758
const { getPubkyClientAsync } = await import('../utils/pubky-client-factory');
5859
this.client = await getPubkyClientAsync();
5960
this.isInitialized = true;
6061

61-
console.log('[Graphiti Offscreen] Pubky client initialized successfully');
62+
logger.info('Offscreen', 'Pubky client initialized successfully');
6263

6364
// Set up message listener
6465
this.setupMessageListener();
6566
} catch (error) {
66-
console.error('[Graphiti Offscreen] Failed to initialize Pubky client:', error);
67+
logger.error('Offscreen', 'Failed to initialize Pubky client', error as Error);
6768
}
6869
}
6970

@@ -90,20 +91,20 @@ class OffscreenHandler {
9091
return false;
9192
}
9293

93-
console.log('[Graphiti Offscreen] Received message:', message.type);
94+
logger.info('Offscreen', 'Received message', { type: message.type });
9495

9596
// Handle async operations
9697
this.handleMessage(message)
9798
.then(sendResponse)
9899
.catch((error) => {
99-
console.error('[Graphiti Offscreen] Error handling message:', error);
100+
logger.error('Offscreen', 'Error handling message', error as Error);
100101
sendResponse({ success: false, error: error.message });
101102
});
102103

103104
return true; // Keep channel open for async response
104105
});
105106

106-
console.log('[Graphiti Offscreen] Message listener registered');
107+
logger.info('Offscreen', 'Message listener registered');
107108
}
108109

109110
/**
@@ -201,11 +202,11 @@ class OffscreenHandler {
201202
});
202203
}
203204

204-
console.log('[Graphiti Offscreen] Annotation synced:', fullPath);
205+
logger.info('Offscreen', 'Annotation synced', { fullPath });
205206

206207
return { success: true, data: { postUri: fullPath } };
207208
} catch (error) {
208-
console.error('[Graphiti Offscreen] Failed to sync annotation:', error);
209+
logger.error('Offscreen', 'Failed to sync annotation', error as Error);
209210
return { success: false, error: (error as Error).message };
210211
}
211212
}
@@ -259,11 +260,11 @@ class OffscreenHandler {
259260
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
260261
}
261262

262-
console.log('[Graphiti Offscreen] Drawing synced:', fullPath);
263+
logger.info('Offscreen', 'Drawing synced', { fullPath });
263264

264265
return { success: true, data: { pubkyUrl: fullPath } };
265266
} catch (error) {
266-
console.error('[Graphiti Offscreen] Failed to sync drawing:', error);
267+
logger.error('Offscreen', 'Failed to sync drawing', error as Error);
267268
return { success: false, error: (error as Error).message };
268269
}
269270
}
@@ -335,14 +336,14 @@ class OffscreenHandler {
335336
}
336337
}
337338

338-
console.log('[Graphiti Offscreen] Sync complete:', { annotationsSynced, drawingsSynced });
339+
logger.info('Offscreen', 'Sync complete', { annotationsSynced, drawingsSynced });
339340

340341
return {
341342
success: true,
342343
data: { annotationsSynced, drawingsSynced }
343344
};
344345
} catch (error) {
345-
console.error('[Graphiti Offscreen] Failed to sync all pending:', error);
346+
logger.error('Offscreen', 'Failed to sync all pending', error as Error);
346347
return { success: false, error: (error as Error).message };
347348
}
348349
}
@@ -385,7 +386,7 @@ class OffscreenHandler {
385386
}
386387
};
387388
} catch (error) {
388-
console.error('[Graphiti Offscreen] Failed to get sync status:', error);
389+
logger.error('Offscreen', 'Failed to get sync status', error as Error);
389390
return { success: false, error: (error as Error).message };
390391
}
391392
}
@@ -394,5 +395,5 @@ class OffscreenHandler {
394395
// Initialize the offscreen handler
395396
new OffscreenHandler();
396397

397-
console.log('[Graphiti Offscreen] Offscreen document loaded');
398+
logger.info('Offscreen', 'Offscreen document loaded');
398399

src/sidepanel/App.tsx

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@ function App() {
2525
const [hasMorePosts, setHasMorePosts] = useState(true);
2626
const [postsPage, setPostsPage] = useState(0);
2727
// @ts-ignore - postsCursor is set for future cursor-based pagination
28+
// This will be used when implementing cursor-based pagination for large feeds
2829
const [postsCursor, setPostsCursor] = useState<string | undefined>(undefined);
2930
const POSTS_PER_PAGE = 20;
3031
const sentinelRef = useRef<HTMLDivElement>(null);
32+
const [highlightedAnnotationId, setHighlightedAnnotationId] = useState<string | null>(null);
3133

3234
useEffect(() => {
3335
initializePanel();
@@ -87,8 +89,9 @@ function App() {
8789
// Listen for messages to scroll to annotations or switch tabs
8890
const handleMessage = (message: any) => {
8991
if (message.type === 'SCROLL_TO_ANNOTATION') {
92+
logger.info('SidePanel', 'Scroll to annotation requested', { annotationId: message.annotationId });
9093
setActiveTab('annotations');
91-
// Scroll logic will be handled in the render
94+
setHighlightedAnnotationId(message.annotationId);
9295
}
9396

9497
if (message.type === 'SWITCH_TO_ANNOTATIONS') {
@@ -103,6 +106,26 @@ function App() {
103106
};
104107
}, []);
105108

109+
// Scroll to annotation when it's loaded and highlighted
110+
useEffect(() => {
111+
if (highlightedAnnotationId && annotations.length > 0) {
112+
// Wait for DOM to update, then scroll
113+
setTimeout(() => {
114+
const annotationElement = document.querySelector(`[data-annotation-id="${highlightedAnnotationId}"]`);
115+
if (annotationElement) {
116+
annotationElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
117+
// Add a highlight class for visual feedback
118+
annotationElement.classList.add('ring-2', 'ring-[#667eea]', 'ring-offset-2', 'ring-offset-[#1F1F1F]');
119+
// Remove highlight after a few seconds
120+
setTimeout(() => {
121+
annotationElement.classList.remove('ring-2', 'ring-[#667eea]', 'ring-offset-2', 'ring-offset-[#1F1F1F]');
122+
setHighlightedAnnotationId(null);
123+
}, 3000);
124+
}
125+
}, 100);
126+
}
127+
}, [highlightedAnnotationId, annotations]);
128+
106129
// Keyboard shortcut listener for Shift+?
107130
useEffect(() => {
108131
const handleKeyDown = (e: KeyboardEvent) => {

0 commit comments

Comments
 (0)