GitHub Desktop 通知模块 desktop-notifications 实践指南:从 API 到 Windows/macOS 原生实现
2026/9/20 12:02:52 网站建设 项目流程
  • 桌面应用
  • 版本控制
  • 开发工具

【免费下载链接】desktop

Focus on what matters instead of fighting with Git.

项目地址:https://gitcode.com/gh_mirrors/de/desktop
点击查看免费下载

desktop-notifications 是 GitHub Desktop 内置的一个零依赖、面向 Windows 与 macOS 的 OS 原生通知库,本文以其官方文档 docs/index.md 为核心,结合其 TypeScript 封装层(vendor/desktop-notifications/lib)与原生 C++ 实现(vendor/desktop-notifications/src)深入剖析初始化、弹通知、事件回调、权限管理与本地构建的全过程。读完本文,你将掌握该库的完整 API 用法、toastActivatorClsid参数的真实作用、平台能力探测逻辑,以及如何在当前仓库的vendor/desktop-notifications子目录下完成原生模块的编译配置。

为什么 GitHub Desktop 要自研通知库

在开始 API 之前,先理解这个库存在的理由。根据 vendor/desktop-notifications/README.md 的说明,GitHub Desktop 团队在评估现有方案后决定自研,原因是它们都有难以接受的短板:

  • Electron:不支持 Windows 通知被折叠进操作中心(Action Center)后的场景,因为 Electron 缺少基于 CLSID 的 COM activator,无法利用 CLSID 激活机制;
  • node-notifier:依赖 snoretoast 处理 Windows 通知,每次通知只能检测一个事件,且必须使用 snoretoast 中硬编码的同一个 CLSID;
  • electron-windows-notifications:围绕 NodeRT 有大量依赖,构建还需要不少手工步骤。

因此该库的设计目标非常明确:零依赖、对 Windows 通知有良好支持、尽可能复用 TypeScript 声明,并且“只实现 GitHub Desktop 需要的功能,而不是 1:1 复刻任何其他通知 API”。这也是理解下文所有 API 设计的前提——它是一套精简、实用、opinionated 的接口。

API 快速上手:文档中的经典用法

官方文档 docs/index.md 给出了完整的最小可运行示例,这是理解整个库的入口:

import { initializeNotifications, DesktopNotification, terminateNotifications, } from 'desktop-notifications' // Initialize the notifications environment with the CLSID activator initializeNotifications('{YOUR-TOAST-ACTIVATOR-CLSID-GOES-HERE}') // ... // Create and configure your notification const notification = new DesktopNotification( 'This is a title', 'This is a body' ) // Set a handler for click events notification.onclick = () => { console.log('Hello world!') } // Then show it! notification.show() // ... // Finally, clean up any resources used by the notifications environment terminateNotifications()

整个生命周期可以归纳为三步:

  1. 初始化:调用initializeNotifications,传入 Windows 平台的toastActivatorClsid,建立通知环境;
  2. 创建并展示:实例化DesktopNotification,通过onclick挂接点击事件,调用show()弹出系统通知;
  3. 清理:所有通知使用完毕后调用terminateNotifications()释放原生资源。

需要特别说明的是:文档中的DesktopNotification类式 API 属于早期设计。在当前仓库的 lib/index.ts 中,实际导出的是函数式 APIinitializeNotificationsshowNotificationcloseNotificationterminateNotificationsgetNotificationsPermissionrequestNotificationsPermission,以及能力探测函数supportsNotificationssupportsNotificationsPermissionRequest和设置页跳转函数getNotificationSettingsUrl。文档展示了库的设计初衷,而源码反映了它的最终形态,二者结合阅读可以看清 API 的演进脉络。

初始化与 toastActivatorClsid:Windows 通知激活的核心

initializeNotifications在 native-module.ts 中实现,其签名为:

export const initializeNotifications: ( opts: INotificationOptions ) => void = opts => getNativeModule()?.initializeNotifications(notificationCallback, opts)

注意两点实现细节:

  • 原生模块懒加载_nativeModule初始为undefined,首次调用时通过supportsNotifications()判断平台是否支持,支持才require('../build/Release/desktop-notifications.node')。源码注释明确说明这是为了“避免启动时崩溃——这种崩溃更难追踪”;
  • 回调先行注册:封装层在调用原生initializeNotifications时,会把统一的notificationCallback作为第一个参数传入,后续原生层的事件都汇流到这个回调再分发。

初始化参数的唯一成员定义在 notification-options.ts:

export interface INotificationOptions { /** CLSID used by Windows to report notification events */ readonly toastActivatorClsid?: string }

toastActivatorClsid是 Windows 通知体系的关键概念:应用要接收 Toast 通知的点击激活事件,必须在注册表中注册一个 COM activator,并将激活器的 CLSID 写入通知的Activated参数中。系统在用户点击通知时通过该 CLSID 拉起对应组件,这正是文档示例中初始化时就要传入 CLSID 的原因。

在原生侧,main_win.cc 对参数做了严格的运行时校验:

  • 第一个参数必须是函数,否则抛出TypeError: Callback must be a function.
  • 第二个参数必须是对象,且必须包含toastActivatorClsid属性,否则抛出TypeError: The options object must have the "toastActivatorClsid" property.
  • 校验通过后调用Utils::utf8ToWideChar把 CLSID 从 UTF-8 转换为宽字符,再构造DesktopNotificationsManager单例持有。

在 GitHub Desktop 主仓库中,app/src/main-process/notifications.tsapp/src/main-process/main.ts负责在应用启动阶段完成该初始化,CLSID 相关的激活器查找逻辑可参考 find-toast-activator-clsid.ts。

展示、关闭与回调:事件驱动的通知生命周期

showNotification:异步展示并返回通知 ID

export const showNotification: ( title: string, body: string, userInfo?: Record<string, any> ) => Promise<string | null> = async (...args) => { const id = crypto.randomUUID() try { await getNativeModule()?.showNotification(id, ...args) } catch (e) { return null } return id }
  • 每次展示前用crypto.randomUUID()生成唯一 ID,该 ID 就是关闭通知的凭证;
  • 原生调用失败时吞掉异常并返回null(通知展示失败不应拖垮主流程);
  • 返回的 ID 可传给closeNotification(id)主动关闭某条通知;
  • userInfo是可选对象,原生层会把它JSON 序列化成字符串随通知携带,用户点击通知时再原样回传——详见下文回调部分。

原生侧showNotification的实现在 main_win.cc 中,依次校验idtitlebody必须为字符串,userInfo若存在必须是对象,随后JSONStringify序列化并调用desktopNotificationsManager->displayToast(id, title, body, userInfo)。若通知系统尚未初始化,会记录DN_LOG_ERROR("Cannot show notification: notifications not initialized.")并直接返回。

事件回调:目前唯一的事件是 click

回调体系定义在 notification-callback.ts:

export type NotificationCallback< T extends Record<string, any> = Record<string, any> > = (event: DesktopNotificationEvent, id: string, userInfo: T) => void export const onNotificationEvent = < T extends Record<string, any> = Record<string, any> >( callback: NotificationCallback<T> | null ) => { globalNotificationCallback = callback as NotificationCallback }
  • 事件类型目前只有一种:'click'(见 notification-event-type.ts);
  • 通过onNotificationEvent(callback)注册全局处理器,callback会收到(event, id, userInfo)三个参数,其中userInfo就是showNotification时传入的那个对象——这正是“把业务上下文随通知带去、点击时取回”的标准模式;
  • 传入null可以取消注册。

这也解释了文档中notification.onclick的语义演进:无论类式 API 还是函数式 API,“点击通知”都是唯一需要响应的用户事件。在 GitHub Desktop 中,app/src/lib/stores/notifications-store.ts就利用该回调把点击事件映射回具体的仓库与 PR 场景。

权限管理:查询、申请与引导用户去系统设置

该库把权限抽象为三种状态,定义在 notification-permission.ts:

export type DesktopNotificationPermission = 'default' | 'granted' | 'denied'
  • 'default':用户尚未做出选择。注释特别说明,在 Windows 上该状态等同于 granted
  • 'granted':已授予通知权限;
  • 'denied':已拒绝通知权限。

配套 API 为:

/** Gets the current state of the notifications permission. */ export const getNotificationsPermission: () => Promise< DesktopNotificationPermission > = () => getNativeModule()?.getNotificationsPermission() /** Requests the user to grant permission to display notifications. */ export const requestNotificationsPermission: () => Promise<boolean> = () => getNativeModule()?.requestNotificationsPermission()
  • getNotificationsPermission()异步读取当前权限状态;
  • requestNotificationsPermission()向系统发起权限申请,返回Promise<boolean>表示是否获得授权。

如果用户拒绝授权,可以用getNotificationSettingsUrl()生成跳转到系统通知设置页的特殊 URL(见 notification-settings-url.ts):

return process.platform === 'darwin' ? 'x-apple.systempreferences:com.apple.preference.notifications' : 'ms-settings:notifications'

macOS 返回系统偏好设置的通知面板 URL,Windows 返回ms-settings:notifications设置页 URL;在不支持的平台上返回null。GitHub Desktop 中 test-notifications.tsx 与 preferences/notifications.tsx 正是借助这套 API 完成“测试通知”与“权限状态展示 + 跳转设置”的交互。

平台支持矩阵:能力探测的精确判定

该库只在受支持的平台上加载原生模块,判定逻辑集中在 notification-support.ts:

export function supportsNotifications() { if (process.platform === 'darwin') { return supportsDarwinNotifications() } if (process.platform === 'win32') { return supportsWindowsNotifications() } return false }
  • macOS:通过os.release()读取 Darwin 内核版本,要求主版本号 ≥ 18,即macOS 10.14 (Mojave) 及以上
  • Windows:要求majorVersion === 10且 build 号 ≥15063(即Windows 10 Creators Update及以上),因为所依赖的 Toast API 中部分能力在 Creators Update 之前不可用;build 号缺失时按15063保守处理;majorVersion > 10也视为支持;
  • 其他平台一律返回false,此时getNativeModule()返回null,所有 API 调用都会安全地静默失败。

权限申请能力有独立判定:supportsNotificationsPermissionRequest()仅在macOS 10.14+返回true,说明当前只有 macOS 支持运行时申请通知权限(Windows 的权限在系统设置中管理)。

构建与 Setup:Windows 上编译原生模块的环境要求

官方文档的 Setup 章节针对独立仓库的构建流程,在当前仓库中对应的是vendor/desktop-notifications子目录(原生模块的编译配置见 binding.gyp,依赖清单见 package.json):

$ cd vendor/desktop-notifications $ yarn

由于该库会构建原生模块(产物为build/Release/desktop-notifications.node),除了较新版本的 Node.js 之外,Windows 上还需要以下依赖(依据文档记载):

  • Python:文档要求 Python 2.7,并建议安装到默认路径(c:\Python27),否则需要手动为 node-gyp 配置路径;安装时务必勾选Add python.exe to Path选项;
  • C++ 工具链,三选一:
    • Visual C++ Build Tools:安装后执行npm config set msvs_version 2019让 node 使用该工具链;
    • Visual Studio 2019:安装时必须勾选Desktop development with C++工作负载(Node.js 安装原生模块所必需),同样执行npm config set msvs_version 2019
    • 二者均要求安装Windows 10 SDK,这一点文档特别标注了 IMPORTANT。

提示:文档撰写于 Python 2.7 仍是 node-gyp 主流解释器的时期,如今 node-gyp 的 Python 版本要求以你使用的 Node/npm 实际版本为准;这里忠实保留文档原始要求,便于对照历史环境。

这套构建依赖与 GitHub Desktop 主仓库在 Windows 上构建原生依赖的方式一致,相关安装脚本可参考 script/post-install.ts。

跨平台原生实现速览

  • Windows:src/win/main_win.cc 是 N-API 入口,负责参数校验、UTF-8/宽字符转换、DesktopNotificationsManager单例管理;DesktopNotificationsManager.h 与 DesktopNotificationsManager.cpp 实现 Toast 展示与 CLSID 激活事件接收;点击事件的激活器由 DesktopNotificationsActionCenterActivator.h 定义;
  • macOS:src/mac/main_mac.mm 与 GHDesktopNotificationsManager.h/.m 基于NSUserNotification/ 用户通知中心实现,同样遵循“初始化 → 展示 → 回调 → 终止”的统一生命周期。

两个平台的实现都通过 N-API(<napi.h>)暴露给 JavaScript 层,这也正是 README 中“基于 N-API 发布预编译二进制、支持不同 Node/Electron 版本”说法的由来。

结语

desktop-notifications 是一个小而精的范例:TypeScript 层给出安全的类型声明与平台能力探测,原生层用 N-API 精准对接 Windows Toast 与 macOS 通知。官方文档中的三步生命周期(初始化 → 展示/回调 → 清理)在源码中一一对应:initializeNotifications负责注册 CLSID activator,showNotification携带userInfo发出通知,onNotificationEvent'click'事件连同业务数据送回应用。若你需要在 Electron 或 Node 应用中实现 Windows 操作中心可点击的 Toast 通知,docs/index.md 是入口,lib 与 src 则是可直接研读的完整参考实现。

  • 桌面应用
  • 版本控制
  • 开发工具

【免费下载链接】desktop

Focus on what matters instead of fighting with Git.

项目地址:https://gitcode.com/gh_mirrors/de/desktop
点击查看免费下载

相关推荐

上一篇:vim-minimap 项目常见问题解决方案
下一篇:如何使用React Native Navigation构建功能强大的跨平台时钟应用

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询