@@ -8542,124 +8542,19 @@ async function handleMessage(message, sender) {
85428542
85438543 // Notifications (with callbacks: onclick, ondone, timeout, tag)
85448544 case 'GM_notification': {
8545- // Phase 11.11 — progress + buttons (ScriptCat parity).
8546- // Chrome's chrome.notifications API supports both natively:
8547- // - type: 'progress' + progress: 0..100 → progress bar
8548- // - buttons: [{ title, iconUrl }] up to 2 → action buttons with
8549- // chrome.notifications.onButtonClicked routing.
8550- const hasProgress = typeof data.progress === 'number';
8551- const notifOpts = {
8552- type: hasProgress ? 'progress' : 'basic',
8553- iconUrl: data.image || 'images/icon128.png',
8554- // Phase 39.31 — clamp to documented Chrome notification limits
8555- // so the future WECG #935 spec change can't break user scripts
8556- // that pass long titles/messages.
8557- title: _clampString(data.title || 'ScriptVault', SV_NOTIF_TITLE_MAX),
8558- message: _clampString(data.text || '', SV_NOTIF_MESSAGE_MAX),
8559- silent: data.silent || false
8560- };
8561- // Phase 41 — pass `requireInteraction` through so scripts can pin a
8562- // notification until the user acts on it (Tampermonkey/ViolentMonkey
8563- // parity). Chrome 50+ supports this for `basic` and `image` types;
8564- // ignored on `progress`. Boolean coercion guards against truthy-but-
8565- // non-boolean values producing unexpected chrome.notifications shape.
8566- if (typeof data.requireInteraction === 'boolean' && data.requireInteraction) {
8567- notifOpts.requireInteraction = true;
8568- }
8569- if (hasProgress) {
8570- notifOpts.progress = Math.max(0, Math.min(100, Math.floor(data.progress)));
8571- }
8572- // Buttons — Chrome caps at 2; we silently truncate to honour the
8573- // platform contract instead of failing the whole notification.
8574- if (Array.isArray(data.buttons) && data.buttons.length > 0) {
8575- notifOpts.buttons = data.buttons.slice(0, 2).map((b) => ({
8576- title: String(b?.title ?? '').slice(0, 200),
8577- ...(b?.iconUrl ? { iconUrl: b.iconUrl } : {})
8578- }));
8579- }
8580- // Use tag as notification ID for updates
8581- const notifId = data.tag
8582- ? await chrome.notifications.create(data.tag, notifOpts)
8583- : await chrome.notifications.create(notifOpts);
8584- const tabId = sender.tab?.id;
8585- // Track notification for callbacks
8586- if (tabId && (data.hasOnclick || data.hasOndone || data.hasOnbuttonclick)) {
8587- if (!self._notifCallbacks) self._notifCallbacks = new Map();
8588- // Evict oldest if map grows too large (prevents unbounded growth)
8589- if (self._notifCallbacks.size > 500) {
8590- const oldest = self._notifCallbacks.keys().next().value;
8591- self._notifCallbacks.delete(oldest);
8592- }
8593- self._notifCallbacks.set(notifId, {
8594- tabId, scriptId: data.scriptId,
8595- hasOnclick: data.hasOnclick,
8596- hasOndone: data.hasOndone,
8597- hasOnbuttonclick: data.hasOnbuttonclick
8598- });
8599- SessionState.persistNotifCallbacks();
8600- }
8601- // Auto-close after timeout
8602- if (data.timeout && data.timeout > 0) {
8603- if (data.timeout >= 30000) {
8604- // Long timeouts use chrome.alarms to survive service worker shutdown
8605- const alarmName = `notif_clear_${notifId}`;
8606- chrome.alarms.create(alarmName, { delayInMinutes: data.timeout / 60000 });
8607- } else {
8608- setTimeout(() => {
8609- chrome.notifications.clear(notifId).catch(() => {});
8610- if (self._notifCallbacks) {
8611- self._notifCallbacks.delete(notifId);
8612- SessionState.persistNotifCallbacks();
8613- }
8614- }, data.timeout);
8615- }
8616- }
8617- return { success: true, id: notifId };
8545+ if (typeof GMNotificationHandler === 'undefined') return { error: 'GMNotificationHandler not available' };
8546+ return await GMNotificationHandler.handleGMNotificationMessage(action, data, sender);
86188547 }
86198548
86208549 // Phase 11.11 — Update an existing notification by id (tag).
86218550 // Skips fields the caller didn't specify so partial updates don't blank
86228551 // out the title/message. Mirrors chrome.notifications.update() behaviour.
8623- case 'GM_updateNotification': {
8624- if (!data.id) return { success: false, error: 'Missing notification id' };
8625- const updateOpts = {};
8626- if (typeof data.title === 'string') updateOpts.title = data.title;
8627- if (typeof data.text === 'string') updateOpts.message = data.text;
8628- if (typeof data.image === 'string') updateOpts.iconUrl = data.image;
8629- if (typeof data.progress === 'number') {
8630- updateOpts.type = 'progress';
8631- updateOpts.progress = Math.max(0, Math.min(100, Math.floor(data.progress)));
8632- }
8633- if (Array.isArray(data.buttons)) {
8634- updateOpts.buttons = data.buttons.slice(0, 2).map((b) => ({
8635- title: String(b?.title ?? '').slice(0, 200),
8636- ...(b?.iconUrl ? { iconUrl: b.iconUrl } : {})
8637- }));
8638- }
8639- if (typeof data.silent === 'boolean') updateOpts.silent = data.silent;
8640- if (typeof data.requireInteraction === 'boolean') updateOpts.requireInteraction = data.requireInteraction;
8641- try {
8642- const wasUpdated = await chrome.notifications.update(data.id, updateOpts);
8643- return { success: !!wasUpdated };
8644- } catch (e) {
8645- return { success: false, error: e?.message || 'Update failed' };
8646- }
8647- }
8552+ case 'GM_updateNotification':
86488553
86498554 // Phase 11.11 — Programmatically close a notification by id (tag).
8650- case 'GM_closeNotification': {
8651- if (!data.id) return { success: false, error: 'Missing notification id' };
8652- try {
8653- await chrome.notifications.clear(data.id);
8654- if (self._notifCallbacks) {
8655- self._notifCallbacks.delete(data.id);
8656- SessionState.persistNotifCallbacks();
8657- }
8658- return { success: true };
8659- } catch (e) {
8660- return { success: false, error: e?.message || 'Close failed' };
8661- }
8662- }
8555+ case 'GM_closeNotification':
8556+ if (typeof GMNotificationHandler === 'undefined') return { error: 'GMNotificationHandler not available' };
8557+ return await GMNotificationHandler.handleGMNotificationMessage(action, data, sender);
86638558
86648559 // Open tab (with close tracking for onclose callback)
86658560 // Focus tab / close opened tab
0 commit comments