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 + 本地库为准,通知内容不参与状态计算。