Skip to content

Commit a5479c5

Browse files
committed
refactor: extract GM notification handler
1 parent fb8d26b commit a5479c5

14 files changed

Lines changed: 822 additions & 337 deletions

‎background.core.js‎

Lines changed: 6 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)