import type { BlockingExtensionUiRequest, ExtensionUiRequest } from "./types"; interface WindowNotificationLike { onclick: Notification["onclick"]; close: () => void; } interface ServiceWorkerRegistrationLike { showNotification: (title: string, options?: NotificationOptions) => Promise; } export interface BrowserNotificationEnvironment { createWindowNotification: (title: string, options?: NotificationOptions) => WindowNotificationLike; getServiceWorkerRegistration: (() => Promise) | null; } export interface BrowserNotificationOptions { title: string; body: string; sessionUrl: string; onClick: () => void; tag?: string; } export type NotificationDelivery = "service-worker" | "window" | null; type DocumentAttentionState = Pick; export function shouldShowBrowserNotification( attentionState: DocumentAttentionState = document, ): boolean { return attentionState.visibilityState !== "visible" || !attentionState.hasFocus(); } export function isBlockingExtensionUiRequest( request: ExtensionUiRequest, ): request is BlockingExtensionUiRequest { switch (request.method) { case "select": case "confirm": case "input": case "editor": return true; case "custom": return request.closed !== true; default: return false; } } export function claimExtensionAttentionNotification( request: ExtensionUiRequest, notifiedRequestIds: Set, ): request is BlockingExtensionUiRequest { if (!isBlockingExtensionUiRequest(request) || notifiedRequestIds.has(request.id)) return false; notifiedRequestIds.add(request.id); return true; } function getBrowserEnvironment(): BrowserNotificationEnvironment { return { createWindowNotification: (title, options) => new Notification(title, options), getServiceWorkerRegistration: "serviceWorker" in navigator ? () => navigator.serviceWorker.getRegistration() : null, }; } export async function showBrowserNotification( options: BrowserNotificationOptions, environment: BrowserNotificationEnvironment = getBrowserEnvironment(), ): Promise { const notificationOptions: NotificationOptions = { body: options.body, ...(options.tag ? { tag: options.tag } : {}), }; if (environment.getServiceWorkerRegistration) { try { const registration = await environment.getServiceWorkerRegistration(); if (registration) { await registration.showNotification(options.title, { ...notificationOptions, data: { url: options.sessionUrl }, }); return "service-worker"; } } catch { // Fall back to a page notification where the constructor is supported. } } try { const notification = environment.createWindowNotification(options.title, notificationOptions); notification.onclick = () => { notification.close(); options.onClick(); }; return "window"; } catch { // Most mobile browsers expose Notification but require service-worker delivery. return null; } }