@@ -6,18 +6,29 @@ import ConfigStorageService from './services/config-storage-service';
66
77let elementSender : ElementSenderService ;
88let currentConfig : ExtensionConfig ;
9+ let initPromise : Promise < void > | null = null ;
910
10- // Initialize when service worker starts
11- async function initialize ( ) {
12- currentConfig = await ConfigStorageService . load ( ) ;
11+ // Idempotent initialization. The MV3 service worker is terminated when idle and
12+ // woken by the very message we need to handle, so the onMessage listener must
13+ // AWAIT this before touching elementSender/currentConfig — otherwise the first
14+ // click after the SW sleeps races the async config load and is silently lost
15+ // (elementSender is still undefined -> TypeError).
16+ function ensureInitialized ( ) : Promise < void > {
17+ if ( ! initPromise ) {
18+ initPromise = ( async ( ) => {
19+ currentConfig = await ConfigStorageService . load ( ) ;
1320
14- // Create the service (no connection on startup)
15- elementSender = new ElementSenderService ( ) ;
21+ // Create the service (no connection on startup)
22+ elementSender = new ElementSenderService ( ) ;
1623
17- logger . info ( '🚀 MCP Pointer background script loaded' , {
18- enabled : currentConfig . enabled ,
19- port : currentConfig . websocket . port ,
20- } ) ;
24+ logger . info ( '🚀 MCP Pointer background script loaded' , {
25+ enabled : currentConfig . enabled ,
26+ port : currentConfig . websocket . port ,
27+ } ) ;
28+ } ) ( ) ;
29+ }
30+
31+ return initPromise ;
2132}
2233
2334// Listen for config changes
@@ -34,42 +45,57 @@ ConfigStorageService.onChange((newConfig: ExtensionConfig) => {
3445 }
3546} ) ;
3647
37- // Listen for messages from content script
48+ // Listen for messages from content script.
49+ // NOTE: the listener itself is registered synchronously at the top level and is
50+ // NOT async; the async work runs in an inner IIFE and we return true to keep the
51+ // channel open (per MV3 messaging guidance).
3852chrome . runtime . onMessage
3953 . addListener ( ( request : any , _sender : any , sendResponse : ( response : any ) => void ) => {
4054 if ( request . type === 'DOM_ELEMENT_POINTED' && request . data ) {
41- // Send element with current port and status callback
42- elementSender . sendElement (
43- request . data ,
44- currentConfig . websocket . port ,
45- ( status , error ) => {
46- // Status flow: CONNECTING -> CONNECTED -> SENDING -> SENT
47- switch ( status ) {
48- case ConnectionStatus . CONNECTING :
49- logger . info ( '🔄 Connecting to WebSocket...' ) ;
50- break ;
51- case ConnectionStatus . CONNECTED :
52- logger . info ( '✅ Connected' ) ;
53- break ;
54- case ConnectionStatus . SENDING :
55- logger . info ( '📤 Sending element...' ) ;
56- break ;
57- case ConnectionStatus . SENT :
58- logger . info ( '✓ Element sent successfully' ) ;
59- break ;
60- case ConnectionStatus . ERROR :
61- logger . error ( '❌ Failed:' , error ) ;
62- break ;
63- default :
64- break ;
65- }
66- } ,
67- ) ;
68-
69- sendResponse ( { success : true } ) ;
55+ ( async ( ) => {
56+ try {
57+ // Ensure config + sender are ready even on a cold SW start.
58+ await ensureInitialized ( ) ;
59+
60+ // Send element with current port and status callback
61+ await elementSender . sendElement (
62+ request . data ,
63+ currentConfig . websocket . port ,
64+ ( status , error ) => {
65+ // Status flow: CONNECTING -> CONNECTED -> SENDING -> SENT
66+ switch ( status ) {
67+ case ConnectionStatus . CONNECTING :
68+ logger . info ( '🔄 Connecting to WebSocket...' ) ;
69+ break ;
70+ case ConnectionStatus . CONNECTED :
71+ logger . info ( '✅ Connected' ) ;
72+ break ;
73+ case ConnectionStatus . SENDING :
74+ logger . info ( '📤 Sending element...' ) ;
75+ break ;
76+ case ConnectionStatus . SENT :
77+ logger . info ( '✓ Element sent successfully' ) ;
78+ break ;
79+ case ConnectionStatus . ERROR :
80+ logger . error ( '❌ Failed:' , error ) ;
81+ break ;
82+ default :
83+ break ;
84+ }
85+ } ,
86+ ) ;
87+
88+ sendResponse ( { success : true } ) ;
89+ } catch ( error ) {
90+ logger . error ( '❌ Failed to handle pointed element:' , error ) ;
91+ sendResponse ( { success : false , error : ( error as Error ) . message } ) ;
92+ }
93+ } ) ( ) ;
94+
95+ return true ; // Keep message channel open for async response
7096 }
7197
72- return true ; // Keep message channel open for async response
98+ return true ;
7399 } ) ;
74100
75101// Handle extension install/update
@@ -88,5 +114,5 @@ chrome.runtime.onInstalled.addListener((details) => {
88114 }
89115} ) ;
90116
91- // Start initialization
92- initialize ( ) ;
117+ // Best-effort warm start (messages also trigger ensureInitialized)
118+ ensureInitialized ( ) ;
0 commit comments