Skip to content

Commit d3b7a12

Browse files
fix: Improve notification handling in background script
1 parent 57f6c67 commit d3b7a12

File tree

3 files changed

+110
-61
lines changed

3 files changed

+110
-61
lines changed

src/background/index.ts

Lines changed: 92 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,12 @@ function registerHandlers(): void {
5555
MessageType.EXECUTE_QUERY,
5656
(msg, port) => executionHandler.handleExecuteQuery(msg, port)
5757
)
58-
58+
5959
messageRouter.registerHandler(
6060
MessageType.CANCEL_TASK,
6161
(msg, port) => executionHandler.handleCancelTask(msg, port)
6262
)
63-
63+
6464
messageRouter.registerHandler(
6565
MessageType.RESET_CONVERSATION,
6666
(msg, port) => executionHandler.handleResetConversation(msg, port)
@@ -87,49 +87,49 @@ function registerHandlers(): void {
8787
MessageType.GET_LLM_PROVIDERS,
8888
(msg, port) => providersHandler.handleGetProviders(msg, port)
8989
)
90-
90+
9191
messageRouter.registerHandler(
9292
MessageType.SAVE_LLM_PROVIDERS,
9393
(msg, port) => providersHandler.handleSaveProviders(msg, port)
9494
)
95-
95+
9696
// MCP handlers
9797
messageRouter.registerHandler(
9898
MessageType.GET_MCP_SERVERS,
9999
(msg, port) => mcpHandler.handleGetMCPServers(msg, port)
100100
)
101-
101+
102102
messageRouter.registerHandler(
103103
MessageType.CONNECT_MCP_SERVER,
104104
(msg, port) => mcpHandler.handleConnectMCPServer(msg, port)
105105
)
106-
106+
107107
messageRouter.registerHandler(
108108
MessageType.DISCONNECT_MCP_SERVER,
109109
(msg, port) => mcpHandler.handleDisconnectMCPServer(msg, port)
110110
)
111-
111+
112112
messageRouter.registerHandler(
113113
MessageType.CALL_MCP_TOOL,
114114
(msg, port) => mcpHandler.handleCallMCPTool(msg, port)
115115
)
116-
116+
117117
messageRouter.registerHandler(
118118
MessageType.MCP_INSTALL_SERVER,
119119
(msg, port) => mcpHandler.handleInstallServer(msg, port)
120120
)
121-
121+
122122
messageRouter.registerHandler(
123123
MessageType.MCP_DELETE_SERVER,
124124
(msg, port) => mcpHandler.handleDeleteServer(msg, port)
125125
)
126-
126+
127127
messageRouter.registerHandler(
128128
MessageType.MCP_GET_INSTALLED_SERVERS,
129129
(msg, port) => mcpHandler.handleGetInstalledServers(msg, port)
130130
)
131-
132-
131+
132+
133133
// Plan generation handlers (for AI plan generation in newtab)
134134
messageRouter.registerHandler(
135135
MessageType.GENERATE_PLAN,
@@ -215,7 +215,7 @@ function registerHandlers(): void {
215215
Logging.log(logMsg.source || 'Unknown', logMsg.message, logMsg.level || 'info')
216216
}
217217
)
218-
218+
219219
// Metrics handler
220220
messageRouter.registerHandler(
221221
MessageType.LOG_METRIC,
@@ -224,7 +224,7 @@ function registerHandlers(): void {
224224
Logging.logMetric(event, properties)
225225
}
226226
)
227-
227+
228228
// Heartbeat handler - acknowledge heartbeats to keep connection alive
229229
messageRouter.registerHandler(
230230
MessageType.HEARTBEAT,
@@ -237,7 +237,7 @@ function registerHandlers(): void {
237237
})
238238
}
239239
)
240-
240+
241241
// Panel close handler
242242
messageRouter.registerHandler(
243243
MessageType.CLOSE_PANEL,
@@ -290,33 +290,33 @@ function registerHandlers(): void {
290290
*/
291291
function handlePortConnection(port: chrome.runtime.Port): void {
292292
const portId = portManager.registerPort(port)
293-
293+
294294
// Handle sidepanel connections
295295
if (port.name === 'sidepanel') {
296296
isPanelOpen = true
297297
Logging.log('Background', `Side panel connected`)
298298
Logging.logMetric('side_panel_opened', { source: 'port_connection' })
299299
}
300-
300+
301301
// Register with logging system
302302
Logging.registerPort(port.name, port)
303-
303+
304304
// Set up message listener
305305
port.onMessage.addListener((message: PortMessage) => {
306306
messageRouter.routeMessage(message, port)
307307
})
308-
308+
309309
// Set up disconnect listener
310310
port.onDisconnect.addListener(() => {
311311
portManager.unregisterPort(port)
312-
312+
313313
// Update panel state if this was the sidepanel
314314
if (port.name === 'sidepanel') {
315315
isPanelOpen = false
316316
Logging.log('Background', `Side panel disconnected`)
317317
Logging.logMetric('side_panel_closed', { source: 'port_disconnection' })
318318
}
319-
319+
320320
// Unregister from logging
321321
Logging.unregisterPort(port.name)
322322
})
@@ -327,9 +327,9 @@ function handlePortConnection(port: chrome.runtime.Port): void {
327327
*/
328328
async function toggleSidePanel(tabId: number): Promise<void> {
329329
if (isPanelToggling) return
330-
330+
331331
isPanelToggling = true
332-
332+
333333
try {
334334
if (isPanelOpen) {
335335
// Signal sidepanel to close itself
@@ -345,7 +345,7 @@ async function toggleSidePanel(tabId: number): Promise<void> {
345345
}
346346
} catch (error) {
347347
Logging.log('Background', `Error toggling side panel: ${error}`, 'error')
348-
348+
349349
// Try fallback with windowId
350350
if (!isPanelOpen) {
351351
try {
@@ -371,36 +371,82 @@ async function toggleSidePanel(tabId: number): Promise<void> {
371371
*/
372372
function registerNotificationListeners() {
373373

374-
// event listener to listen for detecting when browser is opened/resumed
375-
chrome.windows.onFocusChanged.addListener((windowId) => {
374+
// key: notificationId , value: windowId
375+
const windowIds = new Map<string, number>();
376376

377-
// windowId is not none that means window is focues
378-
if( windowId !== chrome.windows.WINDOW_ID_NONE ) {
377+
// key: windowId , value: notificationId[]
378+
const notificationIds = new Map<number, Array<string>>();
379379

380-
//clear all notifications because browser is in focus now
381-
chrome.notifications.getAll((notifications) => {
380+
const getNotificationIds = (windowId: number): Array<string> => {
382381

383-
Object.keys(notifications).forEach(id => {
384-
chrome.notifications.clear(id);
385-
})
382+
if( !notificationIds.has(windowId) ) {
383+
return [];
384+
}
386385

387-
})
386+
return notificationIds.get(windowId)!;
387+
388+
}
389+
390+
// event listener to listen for detecting when browser is opened/resumed
391+
chrome.windows.onFocusChanged.addListener(async (windowId) => {
388392

393+
// windowId is not none when all chrome windows
394+
// are out of focus that means no notification needs to be cleared
395+
if (windowId == chrome.windows.WINDOW_ID_NONE) {
396+
return;
389397
}
390-
398+
399+
const data = getNotificationIds(windowId);
400+
401+
data.forEach( notificationId => {
402+
chrome.notifications.clear(notificationId);
403+
} )
404+
391405
});
392406

393407
//handle click of notification
394-
chrome.notifications.onClicked.addListener((noticationId) => {
408+
chrome.notifications.onClicked.addListener((notificationId) => {
395409

396410
// clear notification
397-
chrome.notifications.clear(noticationId);
398411

399-
chrome.windows.getCurrent((window) => {
412+
const windowId = windowIds.get(notificationId);
413+
414+
if( windowId ) {
415+
400416
//open browser window
401-
chrome.windows.update(window.id!, { focused: true });
402-
});
417+
chrome.windows.update( windowId , { focused: true });
418+
windowIds.delete(notificationId);
403419

420+
// Not clearing `notificationId` from `notficationIds` map here becaue
421+
// the above code will open browser window and it will be cleared in the
422+
// `onFocusChange` handler
423+
} else {
424+
console.info("window if not found for notification" , notificationId);
425+
}
426+
427+
chrome.notifications.clear(notificationId);
428+
429+
});
430+
431+
//listener for sending notification
432+
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
433+
if (request.action === "send-notification") {
434+
const windowId = request.windowId;
435+
436+
//setting window if against notification so that it can be used to clear notification when the window opens
437+
chrome.notifications.create(request.options, async (notificationId) => {
438+
if (windowId) {
439+
let existingNotificationIds = getNotificationIds(windowId!);
440+
existingNotificationIds.push(notificationId);
441+
442+
notificationIds.set(windowId!, existingNotificationIds);
443+
windowIds.set(notificationId, windowId!);
444+
console.log("setting window id", notificationId, windowId, windowIds);
445+
}
446+
sendResponse(notificationId);
447+
});
448+
}
449+
return true;
404450
});
405451

406452
}
@@ -463,15 +509,15 @@ function initialize(): void {
463509

464510
// Set up port connection listener
465511
chrome.runtime.onConnect.addListener(handlePortConnection)
466-
512+
467513
// Set up extension icon click handler
468514
chrome.action.onClicked.addListener(async (tab) => {
469515
Logging.log('Background', 'Extension icon clicked')
470516
if (tab.id) {
471517
await toggleSidePanel(tab.id)
472518
}
473519
})
474-
520+
475521
// Set up keyboard shortcut handler
476522
chrome.commands.onCommand.addListener(async (command) => {
477523
if (command === 'toggle-panel') {
@@ -482,24 +528,23 @@ function initialize(): void {
482528
}
483529
}
484530
})
485-
531+
486532
// Clean up on tab removal
487533
chrome.tabs.onRemoved.addListener(async (tabId) => {
488534
// With singleton execution, just log the tab removal
489535
Logging.log('Background', `Tab ${tabId} removed`)
490536
})
491-
537+
492538
// Handle messages from newtab only
493539
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
494540
if (message.type === 'NEWTAB_EXECUTE_QUERY') {
495541
executionHandler.handleNewtabQuery(message, sendResponse)
496542
return true // Keep message channel open for async response
497543
}
498544
})
499-
545+
500546
Logging.log('Background', 'Nxtscape extension initialized successfully')
501547
}
502548

503549
// Initialize the extension
504-
initialize()
505-
550+
initialize()

src/sidepanel/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ export function App() {
111111
title: "Human input needed",
112112
message: humanInputRequest.prompt,
113113
type: 'basic',
114-
iconUrl: 'assets/icon48.png',
114+
iconUrl: chrome.runtime.getURL('assets/icon48.png'),
115115
isClickable: true,
116116
requireInteraction: true,
117117
});

src/sidepanel/hooks/usePushNotification.ts

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,29 @@ export function usePushNotification() {
77

88
/**
99
* Can be called for sending notifications
10+
*
11+
* options parameter is `chrome.notifications.NotificationOptions<true>` because `chrome.notifications.create` function expects it like that
1012
*/
11-
const sendNotification = useCallback(async (options: chrome.notifications.NotificationOptions<true> ): Promise<string> => {
13+
const sendNotification = useCallback(async (options: chrome.notifications.NotificationOptions<true>): Promise<string> => {
1214

1315
return new Promise(async (resolve, reject) => {
14-
1516
try {
16-
17-
const noticationId = `notification-${Date.now()}`;
18-
19-
chrome.notifications.create( noticationId , options, (notificationId) => {
20-
resolve(notificationId);
17+
const { id: windowId } = await chrome.windows.getCurrent();
18+
19+
chrome.runtime.sendMessage({
20+
action: "send-notification",
21+
options,
22+
windowId
23+
}, (response) => {
24+
if (chrome.runtime.lastError) {
25+
reject(new Error(chrome.runtime.lastError.message));
26+
return;
27+
}
28+
resolve(response);
2129
});
22-
23-
} catch (err: any) {
24-
25-
reject(err);
26-
30+
} catch (err) {
31+
reject(err instanceof Error ? err : new Error(String(err)));
2732
}
28-
2933
})
3034

3135
}, []);

0 commit comments

Comments
 (0)