Реализация уведомлений из браузерного расширения
Браузерные расширения могут показывать системные уведомления через chrome.notifications API. Это нативные уведомления ОС, а не всплывашки на странице — они появляются в системном трее даже когда браузер свёрнут.
Разрешение в манифесте
{
"permissions": ["notifications"]
}
Типы уведомлений
basic — заголовок + текст + иконка. Самый распространённый тип.
image — с большим изображением. На macOS не отображается (игнорируется).
list — список элементов. Поддержка зависит от ОС.
progress — прогресс-бар. Полезен для фоновых загрузок.
Создание уведомления
// background/sw.js
async function showNotification(id, options) {
return new Promise((resolve) => {
chrome.notifications.create(id, {
type: 'basic',
iconUrl: chrome.runtime.getURL('icons/icon-128.png'),
title: options.title,
message: options.message,
priority: 1, // 0 = низкий, 1 = обычный, 2 = высокий
requireInteraction: options.persistent ?? false, // не скрывать автоматически
buttons: options.buttons ?? [],
silent: options.silent ?? false
}, resolve);
});
}
// Использование
await showNotification('sync-complete', {
title: 'Синхронизация завершена',
message: 'Добавлено 3 новых записи',
buttons: [{ title: 'Открыть' }]
});
Если передать пустую строку как id — браузер сгенерирует уникальный id и вернёт его через callback.
Уведомление с прогрессом
async function showProgress(jobId, title, progress) {
const exists = await notificationExists(jobId);
if (!exists) {
chrome.notifications.create(jobId, {
type: 'progress',
iconUrl: chrome.runtime.getURL('icons/icon-128.png'),
title,
message: `${progress}%`,
progress
});
} else {
chrome.notifications.update(jobId, {
progress,
message: `${progress}%`
});
}
}
function notificationExists(id) {
return new Promise((resolve) => {
chrome.notifications.getAll((all) => resolve(id in all));
});
}
// Пример использования при загрузке файла
async function downloadWithProgress(url, filename) {
const jobId = `download-${Date.now()}`;
await showProgress(jobId, `Загрузка: ${filename}`, 0);
const response = await fetch(url);
const total = parseInt(response.headers.get('content-length') ?? '0');
const reader = response.body.getReader();
let received = 0;
const chunks = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
received += value.length;
if (total > 0) {
await showProgress(jobId, `Загрузка: ${filename}`, Math.round(received / total * 100));
}
}
chrome.notifications.clear(jobId);
return new Blob(chunks);
}
Обработка кликов по уведомлению
chrome.notifications.onClicked.addListener(async (notificationId) => {
chrome.notifications.clear(notificationId);
// Открываем нужную вкладку или фокусируемся на существующей
if (notificationId.startsWith('new-message-')) {
const messageId = notificationId.split('-').at(-1);
await openOrFocusTab(`/messages/${messageId}`);
}
});
chrome.notifications.onButtonClicked.addListener(async (notificationId, buttonIndex) => {
chrome.notifications.clear(notificationId);
if (notificationId === 'sync-complete' && buttonIndex === 0) {
// Кнопка "Открыть"
await chrome.tabs.create({ url: chrome.runtime.getURL('pages/dashboard.html') });
}
});
async function openOrFocusTab(path) {
const url = chrome.runtime.getURL(`pages/app.html${path}`);
const [existing] = await chrome.tabs.query({ url: `${chrome.runtime.getURL('pages/app.html')}*` });
if (existing) {
await chrome.tabs.update(existing.id, { active: true, url });
await chrome.windows.update(existing.windowId, { focused: true });
} else {
await chrome.tabs.create({ url });
}
}
Уведомления для периодических задач
Типичный паттерн — уведомления от alarm:
chrome.alarms.onAlarm.addListener(async (alarm) => {
if (alarm.name !== 'check-updates') return;
const updates = await fetchUpdates();
if (updates.length === 0) return;
if (updates.length === 1) {
chrome.notifications.create('update-1', {
type: 'basic',
iconUrl: chrome.runtime.getURL('icons/icon-128.png'),
title: updates[0].title,
message: updates[0].body,
contextMessage: new URL(updates[0].url).hostname
});
} else {
chrome.notifications.create('updates-batch', {
type: 'list',
iconUrl: chrome.runtime.getURL('icons/icon-128.png'),
title: `${updates.length} новых обновлений`,
message: '',
items: updates.slice(0, 8).map(u => ({
title: u.title,
message: new URL(u.url).hostname
}))
});
}
});
На Windows уведомления попадают в центр уведомлений. На macOS — в Notification Center. На Linux — через libnotify, внешний вид зависит от DE. Учитывайте это при тестировании: на macOS тип list и image не отображают дополнительный контент.







