news 2026/9/10 22:05:55

使用 @novu/react 为 React 应用集成 Novu Inbox 通知中心:从 Keyless 快速体验到 HMAC 安全加固

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
使用 @novu/react 为 React 应用集成 Novu Inbox 通知中心:从 Keyless 快速体验到 HMAC 安全加固

使用 @novu/react 为 React 应用集成 Novu Inbox 通知中心:从 Keyless 快速体验到 HMAC 安全加固

【免费下载链接】novuThe open-source communication infrastructure for agents and products项目地址: https://gitcode.com/GitHub_Trending/no/novu

@novu/react是 Novu 官方发布的 React SDK,核心交付物是一个开箱即用的<Inbox />通知中心组件,可在几分钟内为 React 应用接入完整的应用内通知能力(未读角标、通知列表、实时推送、偏好设置等)。本文以 packages/react/README.md 为主线,结合仓库源码,系统讲解安装、Keyless 快速试用、真实订阅者接入、自定义后端地址、受控开关、本地化以及 HMAC 加密安全机制,帮助你快速落地一个生产可用的应用内通知中心。

安装与包结构

在 React 应用中安装@novu/react仅需一条命令:

npm install @novu/react

从 packages/react/package.json 可以看到,该包当前版本为3.19.2,其 peerDependencies 要求reactreact-dom^18.0.0 || ^19.0.0 || ^19.0.0-0react-dom为可选),底层唯一的核心依赖是@novu/js(workspace 引用)。包通过exports字段提供了多个子路径入口:

  • @novu/react(默认入口,浏览器端 ESM/CJS)
  • @novu/react/hooks——仅引入 Hooks,适合自定义 UI 场景
  • @novu/react/themes——主题变量
  • @novu/react/server——服务端渲染相关入口
  • @novu/react/internal——内部 API

从 packages/react/src/index.ts 的导出清单看,SDK 除Inbox外还导出BellNotificationsPreferencesNovuProvider等组件,以及useNotificationsuseCountsusePreferencesuseSubscription等 Hooks,本文聚焦 README 主线中的<Inbox />用法。

Keyless 模式:零配置秒级体验

Keyless 模式专为本地测试与快速实验设计:无需任何环境配置,直接引入并渲染<Inbox />即可看到完整的通知中心界面。

import React from 'react'; import { Inbox } from '@novu/react'; export function App() { return <Inbox />; }

这一模式在源码中有明确的实现支撑。查看 Inbox.tsx 可以看到注释:"for keyless we provide an empty string, the api will generate a identifier"——当未传入applicationIdentifier时,SDK 会以空字符串占位;在 session.ts 中,会话初始化后若服务端返回以pk_keyless_前缀开头的应用标识符,SDK 会将其存入localStorage并在后续会话中复用,从而实现"免配置"的持续体验。对应地,types.ts 中subscribersubscriberIdapplicationIdentifier三者均可缺省的类型分支就是 Keyless 模式。

连接真实订阅者:applicationIdentifier 与 subscriber

要把通知中心接入你的 Novu 环境和真实用户,需要传入两个关键属性:

  • applicationIdentifier:应用标识符,相当于 API 通信的"公钥",可在 Novu Dashboard 的 API Keys 页面获取;
  • subscriber:订阅者 ID,即你业务系统中用户的唯一标识(通常是数据库中的用户 ID)。
import { Inbox } from '@novu/react'; function Novu() { return ( <Inbox subscriber='SUBSCRIBER_ID' applicationIdentifier='APPLICATION_IDENTIFIER' /> ); }

需要注意,subscribersubscriberId在类型上是互斥的:types.ts 中subscriberId被标记为@deprecated(兼容历史版本,见 NV-5801),推荐统一使用subscriberprop。在 Inbox.tsx 中,SDK 会通过buildSubscriber将两者归一化为标准Subscriber对象,再交给InternalNovuProvider创建Novu实例,最终在session.initialize阶段带上subscriberId与浏览器时区(见 session.ts)与服务端建立会话。

自定义后端与 Socket 地址

默认情况下,SDK 使用 Novu 托管的 API 与 Socket 服务。在自托管(self-hosted)场景中,可以通过backendUrlsocketUrl覆盖默认地址:

import { Inbox } from '@novu/react'; function Novu() { return ( <Inbox backendUrl='YOUR_BACKEND_URL' socketUrl='YOUR_SOCKET_URL' subscriber='SUBSCRIBER_ID' applicationIdentifier='APPLICATION_IDENTIFIER' /> ); }

这两个属性连同socketOptions(如自定义重连策略)、apiUrl等一起被组装进NovuOptions(见 Inbox.tsx),在 NovuProvider.tsx 中传给底层new Novu({ ... })构造器。若你的 Novu 实例由上层NovuProvider统一提供,Inbox会通过useUnsafeNovu直接复用该实例,避免重复初始化。

受控 Inbox:用 open 属性管理弹层状态

默认的 Inbox 弹层(popover)由组件内部管理开关。如果希望完全掌控开关时机(例如在导航栏的按钮上控制),可以传入open属性实现受控模式:

import { Inbox } from '@novu/react'; function Novu() { const [open, setOpen] = useState(false); return ( <div> <Inbox subscriber='SUBSCRIBER_ID' applicationIdentifier='APPLICATION_IDENTIFIER' open={isOpen} /> <button onClick={() => setOpen(true)}>Open Inbox</button> <button onClick={() => setOpen(false)}>Close Inbox</button> </div> ); }

open只是DefaultInboxProps中的可选布尔属性之一。结合 types.ts,<Inbox />还支持placement(弹层位置)与placementOffset(偏移量),以及一系列渲染回调:renderNotification(整体自定义通知项)、renderAvatar/renderSubject/renderBody(分别定制头像、标题与正文)、renderDefaultActions/renderCustomActions(默认/自定义操作按钮)、renderBell(自定义铃铛)和点击回调onNotificationClickonPrimaryActionClickonSecondaryActionClick。这些回调通过 Renderer.tsx 与 NovuUI.tsx 中的mountComponent机制注入到底层 UI 引擎,实现 React 渲染与声明式 UI 的双向桥接。

本地化:localization 属性

通过localization属性可以替换 Inbox 界面文案,支持任意语言:

import { Inbox } from '@novu/react'; function Novu() { return ( <Inbox subscriber='SUBSCRIBER_ID' applicationIdentifier='APPLICATION_IDENTIFIER' localization={{ 'inbox.status.archived': 'Archived', 'inbox.status.unread': 'Unread', 'inbox.status.options.archived': 'Archived', 'inbox.status.options.unread': 'Unread', 'inbox.status.options.unreadRead': 'Unread/Read', 'inbox.status.unreadRead': 'Unread/Read', 'inbox.title': 'Inbox', 'notifications.emptyNotice': 'No notifications', locale: 'en-US', }} /> ); }

localization的类型InboxLocalization@novu/js/ui定义并从 index.ts 重新导出,覆盖通知列表状态(未读/已读/归档)、标题、空状态提示、操作项等多个文案键;locale键则用于指定日期等本地化格式。运行时,该对象会在 NovuUI.tsx 中通过novuUI.updateLocalization(options.localization)动态下发给 UI 引擎,无需重新挂载组件即可生效。

HMAC 加密:防止订阅者身份伪造

为什么需要 HMAC

applicationIdentifier是公开的"公钥",任何拿到它的人都可以直接调用 API。如果攻击者把请求中的subscriberId换成其他用户的 ID,就能越权读取他人的通知流。HMAC(Hash-Based Message Authentication Codes)加密用你的**服务端密钥(API Key)**对subscriberId进行签名,使服务端能够校验该订阅者 ID 是否由你的后端合法签发,从而杜绝恶意用户冒充他人。

启用 HMAC 加密

在 Novu 管理后台的In-App(应用内)设置页面,为当前环境开启 HMAC encryption 即可。

订阅者 HMAC:服务端生成、客户端携带

在服务端(Node.js)使用密钥对subscriberId生成 SHA-256 HMAC 十六进制摘要:

import { createHmac } from 'crypto'; const subscriberHash = createHmac('sha256', process.env.NOVU_API_KEY).update(subscriberId).digest('hex');

然后将明文subscriberId与哈希值一并传给客户端组件:

<Inbox subscriber={'SUBSCRIBER_ID_PLAIN_VALUE'} subscriberHash={'SUBSCRIBER_ID_HASH_VALUE'} applicationIdentifier={'APPLICATION_IDENTIFIER'} />

注意:如果 In-App 提供商设置中已启用 HMAC 加密,而subscriberHashsubscriberId未同时提供,Inbox 将无法加载。

从实现上看,subscriberHash会一路传递到会话初始化:在 session.ts 中,subscriberHashcontextHash被原样送入initializeSession请求体,由服务端用密钥重新计算比对,校验通过后才建立会话。

Context HMAC:保护附加数据(可选)

如果通过context属性传递附加数据(如租户信息、环境标识等),攻击者同样可能篡改这些数据,因此需要额外生成contextHash

import { createHmac } from 'crypto'; import { canonicalize } from '@tufjs/canonical-json'; const context = { tenant: 'acme', app: 'dashboard' }; const contextHash = createHmac('sha256', process.env.NOVU_API_KEY) .update(canonicalize(context)) .digest('hex');

contextcontextHash一起传给组件:

<Inbox subscriber={'SUBSCRIBER_ID_PLAIN_VALUE'} subscriberHash={'SUBSCRIBER_ID_HASH_VALUE'} context={{ tenant: 'acme', app: 'dashboard' }} contextHash={'CONTEXT_HASH_VALUE'} applicationIdentifier={'APPLICATION_IDENTIFIER'} />

注意:启用 HMAC 加密且提供了context时,contextHash为必填。由于使用@tufjs/canonical-json做规范化序列化,哈希与键的顺序无关——{a:1, b:2}{b:2, a:1}会生成相同的哈希,服务端与客户端按同一规则计算即可稳定比对。

在 types.ts 中,subscriberHashcontextHash均为可选字符串属性;二者在 Inbox.tsx 中进入providerProps,最终成为NovuOptions的一部分。SDK 侧并不自行计算哈希——签名计算必须在持有密钥的服务端完成,密钥绝不可下发到浏览器端。

进阶:从组件到 Hooks 的自由度

README 的核心是<Inbox />开箱即用组件;如果需要完全自研 UI,@novu/react/hooks子路径提供了数据层 Hooks。例如 useNotifications.ts 支持按tagsreadarchivedseenseveritycreatedGte/createdLte等条件过滤拉取通知,返回notificationsisLoadinghasMore,以及readAllseenAllarchiveAllarchiveAllReadrefetchfetchMore等操作;useCounts.ts 则通过filters数组统计各类未读/未读数。两者都默认通过 WebSocket 监听notifications.notification_receivednotifications.unread_count_changed等事件实时更新,也可传入realtime: false关闭内置实时订阅、改用refetch()自行驱动(对应NovuProviderrealtime配置,见 NovuProvider.tsx)。这正是 docs/platform/quickstart/react.mdx 中"10 分钟接入 React 实时应用内通知"能力背后的数据层基础。

小结

@novu/react<Inbox />用最少的代码覆盖了通知中心的完整闭环:Keyless 模式用于快速验证,applicationIdentifier+subscriber接入真实用户,backendUrl/socketUrl适配自托管部署,open属性实现受控弹层,localization完成多语言,HMAC 体系(subscriberHash+contextHash)则从身份与数据两个维度堵住越权与篡改风险。若默认 UI 无法满足定制需求,还可借助render*回调与/hooks子路径逐步替换为完全自研的界面。

【免费下载链接】novuThe open-source communication infrastructure for agents and products项目地址: https://gitcode.com/GitHub_Trending/no/novu

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 22:03:36

Hypervisor2在智能汽车虚拟化中的关键技术与实践

1. Hypervisor2与现代智能汽车系统的技术耦合 在汽车电子架构从分布式向集中式演进的浪潮中&#xff0c;Hypervisor技术正经历着从基础虚拟化到功能安全的质变。作为第二代虚拟化方案的典型代表&#xff0c;Hypervisor2通过Type-1型架构直接运行在硬件层上&#xff0c;相比传统…

作者头像 李华
网站建设 2026/9/10 22:02:44

CANN/ge Tiling下沉设计

Tiling 下沉&#xff08;Tiling Sink&#xff09;特性分析 【免费下载链接】ge GE&#xff08;Graph Engine&#xff09;是面向昇腾的图编译器和执行器&#xff0c;提供了计算图优化、多流并行、内存复用和模型下沉等技术手段&#xff0c;加速模型执行效率&#xff0c;减少模型…

作者头像 李华
网站建设 2026/9/10 21:59:45

Java千万级数据导出优化方案与实战

1. 千万级数据导出的核心挑战当数据量达到千万级别时&#xff0c;传统的Java导出方案会面临三个致命瓶颈&#xff1a;内存溢出风险、响应超时问题以及文件生成效率低下。我去年主导的某金融报表系统重构项目就遇到过类似场景——当用户尝试导出6个月交易记录时&#xff08;约12…

作者头像 李华