Реализация системных уведомлений в десктоп-приложении

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Реализация системных уведомлений в десктоп-приложении
Простая
от 1 рабочего дня до 3 рабочих дней
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Реализация системных уведомлений в десктоп-приложении

Системные уведомления — один из немногих каналов, через который десктоп-приложение общается с пользователем даже когда окно свёрнуто. На практике реализация упирается в три разных API в зависимости от стека: Electron (Node.js Notification + нативный модуль), Tauri (tauri-plugin-notification), или браузерный Notification API для PWA. Каждый путь со своими ограничениями — рассмотрим все три.

Electron: нативные уведомления

В Electron за уведомления отвечает класс Notification из electron package — он оборачивает нативные механизмы каждой ОС (WinRT Toast на Windows 10/11, NSUserNotificationCenter / UNUserNotificationCenter на macOS, libnotify на Linux).

// main/notifications.ts
import { Notification, nativeImage, app } from 'electron'
import path from 'path'

export interface NotificationOptions {
  title: string
  body: string
  icon?: string
  urgency?: 'normal' | 'critical' | 'low'
  actions?: Array<{ type: 'button'; text: string }>
  timeoutType?: 'default' | 'never'
  toastXml?: string // Windows-only: raw Toast XML
}

export function sendNotification(opts: NotificationOptions): Notification {
  const iconPath = opts.icon
    ? nativeImage.createFromPath(path.resolve(opts.icon))
    : nativeImage.createFromPath(
        path.join(app.getAppPath(), 'resources', 'icon.png')
      )

  const n = new Notification({
    title: opts.title,
    body: opts.body,
    icon: iconPath,
    urgency: opts.urgency ?? 'normal',
    timeoutType: opts.timeoutType ?? 'default',
    actions: opts.actions,
    toastXml: opts.toastXml,
  })

  n.on('click', () => {
    // восстановить/сфокусировать главное окно
    const { BrowserWindow } = require('electron')
    const win = BrowserWindow.getAllWindows()[0]
    if (win) {
      if (win.isMinimized()) win.restore()
      win.focus()
    }
  })

  n.on('action', (_event, index) => {
    console.log(`Action clicked: ${index}`)
  })

  n.show()
  return n
}

На Windows 10+ уведомления отображаются в Action Center. Чтобы они там появлялись корректно, приложению нужен Application User Model ID (AppUserModelID). Electron устанавливает его автоматически через app.setAppUserModelId():

// main/index.ts
import { app } from 'electron'

app.setAppUserModelId('com.yourcompany.yourapp')
// Должно быть вызвано до первого show() уведомления

Без этого вызова уведомления на Windows могут не группироваться и пропадать из центра уведомлений после перезапуска.

IPC: отправка уведомлений из renderer

Renderer-процесс не имеет прямого доступа к Notification из electron — только main process. Правильная схема: renderer -> ipcRenderer.invoke -> main -> Notification.

// preload/index.ts
import { contextBridge, ipcRenderer } from 'electron'

contextBridge.exposeInMainWorld('notifications', {
  send: (opts: NotificationOptions) =>
    ipcRenderer.invoke('notification:send', opts),
})
// main/ipc-handlers.ts
import { ipcMain } from 'electron'
import { sendNotification } from './notifications'

ipcMain.handle('notification:send', (_event, opts) => {
  sendNotification(opts)
})
// renderer/src/hooks/useNotification.ts
declare global {
  interface Window {
    notifications: {
      send: (opts: NotificationOptions) => Promise<void>
    }
  }
}

export function useNotification() {
  const send = (opts: NotificationOptions) => {
    if (window.notifications) {
      window.notifications.send(opts)
    } else {
      // fallback для dev-режима в браузере
      new Notification(opts.title, { body: opts.body })
    }
  }
  return { send }
}

Tauri: плагин уведомлений

В Tauri 2.x уведомления реализуются через tauri-plugin-notification. Добавление:

# src-tauri/Cargo.toml
[dependencies]
tauri-plugin-notification = "2"
// src-tauri/src/main.rs (регистрация плагина)
tauri::Builder::default()
    .plugin(tauri_plugin_notification::init())
    .run(tauri::generate_context!())
    .expect("error while running tauri application");
// src/lib/notifications.ts
import {
  isPermissionGranted,
  requestPermission,
  sendNotification,
} from '@tauri-apps/plugin-notification'

export async function notify(title: string, body: string) {
  let permissionGranted = await isPermissionGranted()

  if (!permissionGranted) {
    const permission = await requestPermission()
    permissionGranted = permission === 'granted'
  }

  if (permissionGranted) {
    sendNotification({ title, body })
  }
}

На macOS Tauri-приложения требуют подписи (code signing) для показа уведомлений в production. В dev-режиме уведомления работают через браузерный API.

Браузерный Notification API (PWA / Electron renderer fallback)

Для PWA и как запасной вариант в renderer без IPC:

// src/services/notification.service.ts
export class NotificationService {
  private static permission: NotificationPermission = 'default'

  static async requestPermission(): Promise<boolean> {
    if (!('Notification' in window)) return false
    if (Notification.permission === 'granted') return true
    if (Notification.permission === 'denied') return false

    const result = await Notification.requestPermission()
    this.permission = result
    return result === 'granted'
  }

  static async send(
    title: string,
    options: NotificationOptions & {
      onClick?: () => void
    } = {}
  ): Promise<Notification | null> {
    const granted = await this.requestPermission()
    if (!granted) return null

    const { onClick, ...nativeOptions } = options
    const notification = new Notification(title, nativeOptions)

    if (onClick) {
      notification.onclick = () => {
        window.focus()
        onClick()
      }
    }

    return notification
  }
}

В Service Worker для PWA уведомления отправляются через self.registration.showNotification() — это единственный способ показать их, когда вкладка закрыта:

// service-worker.js
self.addEventListener('push', (event) => {
  const data = event.data?.json() ?? {}
  event.waitUntil(
    self.registration.showNotification(data.title ?? 'Уведомление', {
      body: data.body,
      icon: '/icons/icon-192x192.png',
      badge: '/icons/badge-72x72.png',
      data: { url: data.url },
      actions: data.actions ?? [],
    })
  )
})

self.addEventListener('notificationclick', (event) => {
  event.notification.close()
  const url = event.notification.data?.url ?? '/'
  event.waitUntil(clients.openWindow(url))
})

Очередь и дедупликация

В сложных приложениях уведомления нужно ставить в очередь, чтобы не завалить пользователя. Простая реализация с throttle:

// src/services/notification-queue.ts
interface QueuedNotification {
  id: string
  title: string
  body: string
  timestamp: number
}

export class NotificationQueue {
  private queue: QueuedNotification[] = []
  private shown = new Set<string>()
  private timer: ReturnType<typeof setTimeout> | null = null
  private readonly cooldownMs: number

  constructor(cooldownMs = 3000) {
    this.cooldownMs = cooldownMs
  }

  enqueue(id: string, title: string, body: string) {
    if (this.shown.has(id)) return // дедупликация по ID
    this.queue.push({ id, title, body, timestamp: Date.now() })
    this.process()
  }

  private process() {
    if (this.timer) return
    const item = this.queue.shift()
    if (!item) return

    this.shown.add(item.id)
    new Notification(item.title, { body: item.body })

    // через 10 сек ID снова доступен
    setTimeout(() => this.shown.delete(item.id), 10_000)

    this.timer = setTimeout(() => {
      this.timer = null
      this.process()
    }, this.cooldownMs)
  }
}

Типичные сроки

Базовая интеграция уведомлений через Electron IPC с обработчиком click — от 4 часов. Полная реализация с очередью, дедупликацией, кастомными Toast XML для Windows, поддержкой action-кнопок и тестами — 1–2 рабочих дня.