macOS 通知与 Dock 角标
口径说明
融云公开文档不覆盖 macOS 原生 SDK 与桌面交付链(dmg / 代码签名 / 公证 / 分发 / Gatekeeper);本页为本项目按 Apple 与 Electron 官方实践补齐的接入文档。
macOS 的系统通知、Dock 角标、通知中心交互由宿主工程实现(SDK 只负责消息与未读数据)。推荐在主进程统一收口:消息到达 → 判断是否提醒 → 发系统通知 → 更新 Dock 角标 → 点击回到对应会话。
职责划分
| 能力 | 实现位置 | 说明 |
|---|---|---|
| 系统通知 | 主进程(Electron `Notification`) | Electron 35 在主进程使用系统通知中心;首次发送会触发权限提示 |
| Dock 角标 | 主进程 `app.dock.setBadge` | 只接受字符串;0 或空串清除 |
| 通知点击 | 主进程监听 `click` | 聚焦窗口;如需直达会话,用宿主 preload 扩展一个通道(SDK preload 仍是固定白名单) |
| 免打扰 | 主进程判断会话 `notificationLevel` | level=2 不发通知、不加角标;level=1 仅 @ 提醒 |
| 通知去重 | 主进程按 messageId / 会话聚合 | 同一批离线消息合并为一条“N 条新消息” |
主进程实现
JavaScript
// main.mjs
import { app, BrowserWindow, Notification } from 'electron';
let mainWindow;
const muteLevels = new Map(); // 由渲染进程 / 本地库同步的会话免打扰级别
function shouldNotify({ targetId, mentioned }) {
const level = muteLevels.get(targetId) ?? 0;
if (level === 2) return false; // 免打扰
if (level === 1 && !mentioned) return false; // 仅 @ 提醒
return true;
}
function updateDockBadge(total) {
if (!app.dock) return;
app.dock.setBadge(total > 99 ? '99+' : total > 0 ? String(total) : '');
}
function notifyMessages(payload) {
const { messages, totalUnread } = payload;
const first = messages[0];
if (!first || !shouldNotify({ targetId: first.targetId, mentioned: Boolean(first.mentionedInfo) })) {
updateDockBadge(totalUnread);
return;
}
const notification = new Notification({
title: first.senderName || '新消息',
body: messages.length > 1 ? `${messages.length} 条新消息` : first.content?.text || '[消息]',
silent: false,
});
notification.on('click', () => {
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.show();
mainWindow.focus();
}
// 需要“点击直达会话”时,由宿主 preload 扩展 host 通道(见下方示例);
// 当前只聚焦窗口,不假设额外通道存在。
});
notification.show();
updateDockBadge(totalUnread);
}
// 订阅事件并触发通知(宿主侧)
import { Events } from '@ni-paas-im/electron/main';
service.client.addEventListener(Events.MESSAGES, notifyMessages);宿主 preload:在 SDK 桥之上扩展通知路由
JavaScript
// host-preload.mjs(先加载 SDK preload,再扩展宿主通道)
import '@ni-paas-im/electron/preload';
import { contextBridge, ipcRenderer } from 'electron';
contextBridge.exposeInMainWorld('hostBridge', {
onOpenConversation: (callback) =>
ipcRenderer.on('host:open-conversation', (_event, payload) => callback(payload)),
});JavaScript
// renderer:点击通知后跳转会话(通道由宿主 preload 定义)
window.hostBridge.onOpenConversation(({ conversationType, targetId }) => {
router.openChat({ conversationType, targetId });
});权限与设置
- 首次发通知时系统会询问权限;被拒绝后需引导用户到「系统设置 → 通知 → 应用」开启。
- 「专注模式」会按系统策略静默通知;应用内无法绕过,属预期。
- 通知中心里点击通知应聚焦已有窗口,不要重复新建窗口。
- 退出登录 / 清空未读时同步清除 Dock 角标。
- 通知正文属于敏感信息:可在设置中提供“隐藏消息内容”选项,只显示“新消息”。
不要把通知做成消息通道
通知仅用于提醒;消息正文、未读与历史以主进程 IMLib + 本地库为准,通知内容不参与状态计算。