一、为什么桌面应用需要Web推送通知?

夜深人静时你的代码突然崩溃,用户还蒙在鼓里;活动倒计时只剩5分钟,用户却毫不知情——这时Web推送通知就像个贴心小助手,把关键信息直接送到用户桌面。在Electron应用中实现推送通知,可以让你的跨平台软件拥有类似原生应用的即时通讯能力。

二、搭建基础开发环境

1. 初始化Electron项目

mkdir electron-push-demo && cd electron-push-demo
npm init -y
npm install electron --save-dev

2. 创建主进程文件(main.js)

const { app, BrowserWindow } = require('electron')
const path = require('path')

let mainWindow

app.whenReady().then(() => {
  mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: false,
      contextIsolation: true,
      preload: path.join(__dirname, 'preload.js')
    }
  })

  // 加载本地调试页面
  mainWindow.loadFile('index.html')
})

// 注册服务工作者(Service Worker)
app.on('ready', async () => {
  if (require('electron-squirrel-startup')) return
  const registration = await navigator.serviceWorker.register('sw.js')
})

三、实现推送通知的核心逻辑

1. 配置客户端订阅(renderer.js)

// 请求通知权限
const requestNotificationPermission = async () => {
  const permission = await Notification.requestPermission()
  if (permission === 'granted') {
    console.log('已获得通知权限')
    // 订阅推送服务
    const registration = await navigator.serviceWorker.ready
    const subscription = await registration.pushManager.subscribe({
      userVisibleOnly: true,
      applicationServerKey: urlBase64ToUint8Array('替换为你的VAPID公钥')
    })
    // 将subscription对象发送到服务端保存
    sendSubscriptionToServer(subscription)
  }
}

// URL安全的base64转Uint8Array
const urlBase64ToUint8Array = (base64String) => {
  const padding = '='.repeat((4 - base64String.length % 4) % 4)
  const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/')
  const rawData = window.atob(base64)
  return new Uint8Array([...rawData].map((char) => char.charCodeAt(0)))
}

2. 服务端推送实现(Node.js示例)

const webpush = require('web-push')
const express = require('express')
const app = express()

// 配置VAPID密钥
webpush.setVapidDetails(
  'mailto:your_email@domain.com',
  '你的公钥',
  '你的私钥'
)

// 模拟存储订阅信息的数据库
let subscriptions = []

// 保存订阅信息端点
app.post('/subscribe', express.json(), (req, res) => {
  subscriptions.push(req.body)
  res.status(201).send('订阅成功')
})

// 触发推送端点
app.post('/push', async (req, res) => {
  const payload = JSON.stringify({
    title: '新消息通知',
    body: '您有3条未读提醒',
    icon: '/notification-icon.png'
  })

  // 给所有订阅者发送推送
  const sendPromises = subscriptions.map(subscription => 
    webpush.sendNotification(subscription, payload)
      .catch(err => console.error('推送失败:', err))
  )

  await Promise.all(sendPromises)
  res.send('推送已触发')
})

app.listen(3000, () => console.log('服务器运行中...'))

四、深入理解Service Worker

sw.js文件中添加推送事件处理:

self.addEventListener('push', (event) => {
  const payload = event.data?.json() || { title: '新提醒' }
  
  // 显示系统通知
  event.waitUntil(
    self.registration.showNotification(payload.title, {
      body: payload.body,
      icon: payload.icon,
      vibrate: [200, 100, 200] // 振动模式
    })
  )
})

// 点击通知处理逻辑
self.addEventListener('notificationclick', (event) => {
  event.notification.close()
  clients.openWindow('https://your-app.com/notifications')
})

五、实际应用中的技术细节

1. 跨平台兼容处理

Windows系统默认使用Windows Toast通知,可通过electron-windows-notifications包增强功能:

const { enable } = require('electron-windows-notifications')
enable({ useHardware: true }) // 启用硬件加速通知

2. 调试小技巧

在开发者工具中查看Service Worker状态:

// 渲染进程中执行
navigator.serviceWorker.getRegistrations().then(registrations => {
  registrations.forEach(reg => {
    console.log('Service Worker状态:', reg.active ? '激活' : '未激活')
  })
})

六、技术方案的综合评估

优势分析

  • 跨平台兼容:一套代码支持Windows/macOS/Linux
  • 网络要求低:支持离线场景的本地通知
  • 性能优异:基于系统级通知组件实现

潜在痛点

  • 用户授权难题:首次必须获得授权才能使用
  • 调试复杂:Service Worker更新需要强制刷新
  • 样式局限:无法完全自定义通知界面外观

实用注意事项

  1. 本地开发必须开启HTTPS或使用localhost
  2. VAPID密钥需要定期轮换保证安全
  3. macOS系统需配置NSUserNotification权限
  4. 合理控制通知频率避免用户反感

七、典型应用场景实战

某股票交易软件的实时提醒系统:

// 推送逻辑优化示例
function sendStockAlert(subscription, stockInfo) {
  const payload = {
    title: `${stockInfo.symbol}价格提醒`,
    body: `当前价 ${stockInfo.price} 触发设定阈值`,
    data: {
      url: `/stocks/${stockInfo.symbol}`
    }
  }

  // 添加过期时间保证时效性
  const options = {
    TTL: 3600 // 1小时后过期
  }

  webpush.sendNotification(subscription, JSON.stringify(payload), options)
}

八、安全防护指南

1. 数据加密传输

// 使用Crypto模块加密订阅信息
const crypto = require('crypto')

function encryptSubscription(sub) {
  const iv = crypto.randomBytes(16)
  const cipher = crypto.createCipheriv('aes-256-cbc', 
    Buffer.from(process.env.ENCRYPT_KEY), iv)
  let encrypted = cipher.update(JSON.stringify(sub))
  encrypted = Buffer.concat([encrypted, cipher.final()])
  return iv.toString('hex') + ':' + encrypted.toString('hex')
}

九、总结与展望

桌面推送不仅是技术实现,更是用户体验的重要组成部分。通过Electron的Web Push API,我们可以在保持跨平台优势的同时,获得接近原生应用的实时通知能力。未来随着协议的演进,可能会看到更丰富的交互形式和更强的定制能力。