- 桌面应用
- 版本控制
- 开发工具
【免费下载链接】desktop
Focus on what matters instead of fighting with Git.
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()整个生命周期可以归纳为三步:
- 初始化:调用
initializeNotifications,传入 Windows 平台的toastActivatorClsid,建立通知环境; - 创建并展示:实例化
DesktopNotification,通过onclick挂接点击事件,调用show()弹出系统通知; - 清理:所有通知使用完毕后调用
terminateNotifications()释放原生资源。
需要特别说明的是:文档中的DesktopNotification类式 API 属于早期设计。在当前仓库的 lib/index.ts 中,实际导出的是函数式 API:initializeNotifications、showNotification、closeNotification、terminateNotifications、getNotificationsPermission、requestNotificationsPermission,以及能力探测函数supportsNotifications、supportsNotificationsPermissionRequest和设置页跳转函数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.ts与app/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 中,依次校验id、title、body必须为字符串,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。
- Visual C++ Build Tools:安装后执行
提示:文档撰写于 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.
相关推荐
GitHub事件通知系统:go-github Notifications API实战
GitHub事件通知系统:go github Notifications API实战 引言:为什么需要GitHub事件通知系统? 在现代软件开发流程中,团队协作
后端API设计Fabric 桌面通知(Desktop Notifications)完整指南:配置、跨平台支持与安全实践
Fabric 桌面通知(Desktop Notifications)完整指南:配置、跨平台支持与安全实践 Fabric 是开源的 AI 增强人类工作流框架,其桌
AI 应用人工智能提示工程CLI本地部署LOTUS开发实战:从零构建一个基于语义搜索的智能知识库系统
LOTUS开发实战:从零构建一个基于语义搜索的智能知识库系统 LOTUS是一款强大的语义查询引擎,能够利用LLM技术实现快速、便捷的数据处理。本文将详细介绍如何
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考