Novu Cloud 实时通道:基于 Cloudflare Workers 与 Durable Objects 的 @novu/socket-worker 架构与本地开发指南

发布时间:2026/9/10 6:11:35

Novu Cloud 实时通道:基于 Cloudflare Workers 与 Durable Objects 的 @novu/socket-worker 架构与本地开发指南 Novu Cloud 实时通道基于 Cloudflare Workers 与 Durable Objects 的 novu/socket-worker 架构与本地开发指南【免费下载链接】novuThe open-source communication infrastructure for agents and products项目地址: https://gitcode.com/GitHub_Trending/no/novu本篇文章围绕 Novu 仓库中 enterprise/workers/socket/README.md 展开系统讲解novu/socket-worker——一个承载 Novu Cloud WebSocketsPartySocket实时通道的 Cloudflare Worker Durable Object 服务。你将掌握它的整体架构Hono 路由、Durable Object 房间模型、EU 数据驻留、本地联调方法8787 端口、.dev.vars、与 API/Worker/Playground 的环境接线以及 JWT 认证、内部 API 鉴权、WebSocket Hibernation 与 contextKeys 精确匹配等核心实现原理可直接上手在本地跑通整套实时链路。一、socket-worker 在 Novu 实时体系中的角色Novu 的开源实时通道由apps/wsNode.js 网关基于 Socket.IO/PartySocket 生态与本次要讲的novu/socket-worker组成。后者定位为Novu Cloud 的 WebSocket 实时路径当NOVU_ENTERPRISEtrue时Cloud 环境下的实时消息不再走传统 Node 网关而是由 Cloudflare 边缘上的 Worker Durable Object 承担 WebSocket 升级与消息投递也就是 README 中标注的 Cloudflare Worker Durable Object for Novu Cloud WebSockets (PartySocket)。从 package.json 可以看到该 Worker 的运行时依赖非常精简honoHTTP 路由框架处理 WebSocket 升级、内部消息接口与健康检查ws/types/jsonwebtokenWebSocket 与 JWT 相关类型支撑tsndr/cloudflare-worker-jwt在 Worker 运行时内完成 JWT 签名校验Cloudflare Workers 无 Node 原生crypto完整 API故使用专为 Worker 设计的 JWT 库wrangler^4.49.0本地开发与多环境部署 CLI。二、整体架构与请求路径Worker 的入口是 src/index.ts一个典型的 Hono 应用对外暴露三条路由路由方法中间件职责/GETauthenticateJWT携带?token发起 WebSocket 升级/sendPOSTauthenticateInternalAPI内部服务向指定用户房间推送事件/healthGET无健康检查返回OK未命中路由统一返回 404Not found应用级错误经app.onError记录日志并返回 500。2.1 WebSocket 升级链路当客户端如 playground/web-chat 中的 PartySocket 客户端发起连接时请求经过 middleware/auth.ts 校验?token中的 JWT然后进入 handlers/websocket.ts 的handleWebSocketUpgrade从 JWT payload 与请求中取出userId、subscriberId、organizationId、environmentId、contextKeys计算房间 IDroomId ${environmentId}:${userId}即每个用户在其环境内拥有一个专属房间根据REGION变量决定是否使用 EU 数据驻留命名空间WEBSOCKET_ROOM.jurisdiction(eu)通过idFromName(roomId)定位 Durable Object 实例并stub.fetch(...)把用户信息以X-User-Id、X-Subscriber-Id、X-Organization-Id、X-Environment-Id、X-JWT-Token、X-Context-Keys等自定义头透传给 DO。2.2 消息推送链路API/Worker 需要给在线用户推送实时事件时向/send发起 POST请求体结构由handleSendMessage校验逻辑确认{ userId: 用户 ID字符串, environmentId: 环境 ID字符串, event: 事件名如 notification.inbox_received, data: 任意业务负载, contextKeys: [可选, 上下文键数组] }handleSendMessage会依次校验userId与event必填、environmentId必填、三者必须为字符串随后同样按environmentId:userId定位 Durable Object并通过context.executionCtx.waitUntil(stub.sendToUser(...))异步投递接口立即返回{ success: true, roomId, timestamp }。三、本地开发环境搭建该包属于 pnpm workspace见根目录 pnpm-workspace.yaml因此依赖安装统一在仓库根目录执行pnpm install启动开发服务器有两种等价方式# 方式一仓库根目录运行利用 workspace filter pnpm dev:socket-worker # 方式二进入本包目录直接运行 cd enterprise/workers/socket pnpm run dev根目录 package.json 中dev:socket-worker定义为pnpm --filter novu/socket-worker dev即pnpm run dev执行的是wrangler dev --env local。端口约定务必遵守socket-worker 固定运行在http://127.0.0.1:8787而本地thalamus-observer另一 Cloudflare Worker见 scripts/dev-environment-setup.sh 相关脚本使用8788两者互不冲突。若修改 socket-worker 端口需同步修改下游所有指向它的环境变量。3.1 首次运行前的密钥配置.dev.vars被 gitignore首次需要从模板复制并填写cd enterprise/workers/socket cp .dev.vars.example .dev.vars然后参考 .dev.vars.example 中的注释从apps/api/src/.env取值JWT_SECRETapps/api/src/.env 中的 JWT_SECRET INTERNAL_API_KEYapps/api/src/.env 中的 INTERNAL_SERVICES_API_KEY关键约束INTERNAL_API_KEY必须与 API 侧的INTERNAL_SERVICES_API_KEY完全一致因为/send接口的调用方API/Worker 内部服务正是用它作为 Bearer 凭证不一致将导致推送被 401 拒绝。JWT_SECRET则用于校验客户端 WebSocket 升级时携带的?token签名必须与签发 token 的 API 侧共享同一密钥。localwrangler 环境会将API_URL默认设置为http://127.0.0.1:3000见 wrangler.jsonc用于 Durable Object 回拨 API 上报用户在线状态。四、打通 API / Worker / Playground 的完整环境接线README 给出了三条链路的联调配置。要让本地整套系统API Worker Playground都走 Cloudflare socket需要1) API 与 Worker 侧apps/api/src/.env和apps/worker/src/.envSOCKET_WORKER_URLhttp://127.0.0.1:8787 NOVU_ENTERPRISEtrue # INTERNAL_SERVICES_API_KEY 保持与 .dev.vars 的 INTERNAL_API_KEY 相同2) Playground 侧playground/web-chatNEXT_PUBLIC_NOVU_SOCKET_URLhttp://127.0.0.1:8787 NEXT_PUBLIC_NOVU_SOCKET_TYPEcloud其中NEXT_PUBLIC_NOVU_SOCKET_TYPEcloud指示 Playground 走 Cloudflare 云端 socket 路径而非本地 Node 网关——这正是 README 强调的 Locally, Cloudflare sockets are the realtime path 的含义。配置完成后Playground 发起的 WebSocket 升级请求会携带 JWT 直连 8787 端口的 Worker。五、wrangler 多环境配置与部署wrangler.jsonc 定义了四个环境差异点集中在名称、路由域名、Durable Object 绑定与变量环境Worker 名称自定义域名API_URLREGIONlocalsocket-worker-local无workers_devhttp://127.0.0.1:3000globalstagingsocket-worker-stagingsocket.novu-staging.cohttps://api.novu-staging.coglobalproduction-ussocket-worker-production-ussocket.novu.cohttps://api.novu.coglobalproduction-eusocket-worker-production-eueu.socket.novu.cohttps://eu.api.novu.coeu所有环境都声明了同一个 Durable Object 绑定WEBSOCKET_ROOM → WebSocketRoom并在migrations中以new_sqlite_classes: [WebSocketRoom]tagv1注册SQLite 后端 DO。此外observability.enabled: true开启了 Cloudflare 观测。对应 package.json 中的部署脚本pnpm run deploy # wrangler deploy默认环境 pnpm run deploy:staging pnpm run deploy:production-us pnpm run deploy:production-eu pnpm run deploy:local pnpm run cf-typegen # 从 wrangler 配置生成 Worker 类型声明REGIONeu在生产 EU 环境中的作用非常关键DO 命名空间会调用jurisdiction(eu)将连接与数据锁定在欧盟境内以满足数据驻留要求见下一节源码说明。六、核心实现原理WebSocketRoom Durable ObjectDurable Object 的实现集中在 src/durable-objects/websocket-room.ts类WebSocketRoom是整条实时链路的房间载体。以下是几个值得深入理解的设计点。6.1 基于 Hibernation API 的连接管理构造函数中通过this.ctx.setWebSocketAutoResponse(new WebSocketRequestResponsePair(ping, pong))配置自动心跳应答使 Worker 可在空闲时休眠而连接保持存活。WebSocket 接受使用hibernation 兼容方式const tags [user:${userId}, env:${environmentId}]; this.ctx.acceptWebSocket(server, tags); server.serializeAttachment({ jwtToken, connectedAt: Date.now(), contextKeys });tags给连接打上user:与env:标签后续可定向按标签检索连接serializeAttachment把 JWT、连接时间、contextKeys 持久化到连接附件上附件上限 2KBJWT 通常 1KB这样 DO 休眠唤醒后依然能恢复连接元数据——这正是代码注释强调No need to store JWT tokens in memory的原因。运行时通过三个钩子驱动生命周期webSocketMessage收到客户端消息此处仅校验元数据存在性、webSocketClose关闭连接并触发下线上报、webSocketError记录错误日志。6.2 房间容量与并发保护每个 DO 实例设定了MAX_CONNECTIONS 100的硬上限。fetch在升级前检查this.ctx.getWebSockets().length达到上限返回503 Retry-After: 60让客户端 60 秒后重试。此外还暴露了三个统计方法getActiveConnectionsForUser、getTotalActiveConnections、getConnectionCapacity返回{ current, max, available }接口契约定义在 src/types/index.ts。6.3 contextKeys 精确匹配多上下文隔离投递一个用户可能同时打开多个上下文例如不同的工作区页面/send携带的contextKeys与连接附件中的contextKeys做精确匹配后才投递。isExactMatch的规则是消息 contextKeys 为空数组 → 仅投递给 contextKeys 也为空的连接长度不一致 → 不投递否则逐个成员比较every(key inboxContextKeys.includes(key))全部命中才投递。代码注释明确指出这套逻辑与ws.gateway.ts保持一致保证了新旧实时通道在语义上的兼容。投递过程对消息体只做一次JSON.stringify预序列化{ event, data, timestamp }再对命中连接并行发送并用Promise.allSettled容错。6.4 在线状态回拨 API连接建立与断开时DO 都会通过notifySubscriberOnlineState向API_URL的POST /v1/internal/subscriber-online-state上报请求体含subscriberId、environmentId、isOnline、organizationId、timestamp以Bearer ${jwtToken}鉴权。该调用使用ctx.waitUntil包裹确保 DO 可立即进入休眠而不阻塞连接建立只有所有同用户连接都断开剩余连接数 ≤ 0时才上报离线。七、安全模型双层认证7.1 客户端侧JWT 校验middleware/auth.ts 中authenticateJWT负责升级请求认证从?token查询参数取 JWT缺失返回 401用JWT_SECRET通过tsndr/cloudflare-worker-jwt验签并解码从 payload 提取_id作为 userId、subscriberId缺省回退为 userId、organizationId、environmentId、contextKeys任一缺失返回 401认证信息通过 Honocontext.set注入后续处理。7.2 内部侧常量时间比较middleware/internal-auth.ts 保护/send请求头需携带Authorization: Bearer key与INTERNAL_API_KEY做常量时间比较constantTimeEquals逐字符异或累加长度不等直接失败从实现层面抵御时序侧信道攻击。若服务端未配置INTERNAL_API_KEY则返回 500。八、类型契约与可观测性src/types/index.ts 定义了完整的环境与元数据契约IEnvWEBSOCKET_ROOMDO 命名空间绑定、JWT_SECRET、INTERNAL_API_KEY必填API_URL、REGION可选IConnectionMetadatauserId、environmentId、connectedAt、jwtToken、contextKeys——即 serializeAttachment 持久化的全部字段IWebSocketRoomsendToUser、连接数查询与容量查询接口。运维层面/health提供存活探针wrangler 配置开启了observabilityapp.onError统一记录应用错误。日常排障可结合 Cloudflare Dashboard 的 Worker 日志查看[Internal API] Routing message to room: ...等关键日志行。九、小结novu/socket-worker展示了如何用 Cloudflare Workers Durable Objects 构建生产级实时通道边缘就近升级 WebSocket、以environmentId:userId为粒度分房、Hibernation API 降本增效、JWT 内部 API Key 双层鉴权、EU 数据驻留按区域隔离。对于希望深度定制 Novu Cloud 实时链路或自建同类基础设施的开发者建议从 enterprise/workers/socket/src/index.ts路由入口→ src/handlers/websocket.ts升级与推送→ src/durable-objects/websocket-room.ts房间核心这条调用链入手阅读再结合 wrangler.jsonc 完成本地与多环境部署验证。【免费下载链接】novuThe open-source communication infrastructure for agents and products项目地址: https://gitcode.com/GitHub_Trending/no/novu创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/9/10 6:11:35

ARM汇编性能优化:从optimized-routines看底层计算基元设计

1. 为什么一个“optimized-routines”库值得花三天做静态审计? 在ARM生态里,我们常把“性能优化”挂在嘴边,但多数人只停留在调用 -O3 、换用 armclang 或改几个内联汇编的层面。真正决定系统级吞吐量与能效比的,往往不是顶层…

2026/9/10 7:06:40

AI生成代码时代,能力断层如何弥补?Code to Learn训练闭环实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/10 7:06:40

RK3576开发板RTC完整配置指南:从内核到Android时区避坑

前阵子调一块RK3576开发板,功能问题都处理完了,结果客户那边反馈说设备重启后时间总是回到出厂值,日志时间戳全乱了。查了一圈,发现是RTC这块没配置干净。RK3576这颗芯片在AIoT和边缘计算项目里用得越来越多,配Linux或…

2026/9/10 7:06:40

AI文本太假怎么办?humanizer人性化改写实操指南

早上打开后台,看到一位读者的留言:“能不能出一篇关于 humanizer 的内容?我写文章基本都是 AI 帮我起草,但总觉得发出去的效果不对,说不出来哪里假。”这条留言让我挺有感触。做内容这行几年,我自己也被“A…

2026/9/10 7:01:40

T507平台适配长江存储EC150的工程级兼容性实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/9 13:11:35

超人会飞不算本事:系统稳定依赖清晰规则与边界设计

开头先不绕弯子。“#斯坦李吐槽dc 所以超人是无缘无故会飞的嘛哈哈哈哈哈哈哈锤哥真是技术人才啊!#雷神 #复联”这类调侃式短标题,第一波冲击力在于它把两个宇宙的角色塞进同一个吐槽箱里,但细想一下就能发现,它真正碰到的根本不是…

2026/9/8 7:15:15

超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论

把“蜘蛛侠 vs 超人”放在 CSDN 上聊,可能很多人第一反应是走错片场了。但如果把这两个角色看成“两个持续运营了 80 多年的文化产品”,你会发现,这场比较本质上是两个不同 IP 策略的长期结果对比:超人赢在定义了整个超级英雄题材…

2026/9/9 16:31:09

基于CNN的调制信号识别:MATLAB实现时频图分类实战

简介:本资源是一套面向通信工程与信号处理方向学习者、研究者的深度学习实践方案,聚焦调制信号自动检测与识别这一典型无线通信任务,解决传统方法依赖人工特征、低信噪比下性能下降等痛点。压缩包共12个文件(10.73MB)&…

2026/9/10 0:00:55

目录对比去重实战:用哈希算法精准清理重复文件

我电脑里现在还有一块换了三次机的“数据墓地”硬盘,里面存着2016年以前所有旧笔记本的完整备份。平时不觉得有什么,直到前阵子想把它整理归档,发现同一个安装包、同一批照片、同一份论文草稿,在几个不同的备份目录里反复出现。更…

2026/9/10 0:00:55

Leaflet离线地图完整Demo合集:内网部署与坐标纠偏实战

简介:这是一份面向Web GIS开发者的LeafLet离线地图示例合集,帮助开发者快速掌握离线地图从搭建到交互的完整流程。压缩包共723个文件,大小14.06MB,以319个js脚本、175个html页面和29个css样式文件为主体,配合png/svg图…

2026/9/10 0:00:55

MATLAB读取Rinex 3.02观测文件:多系统GNSS数据解析实战

简介:基于MATLAB开发的Rinex3.02版观测文件(o文件)读取代码包,面向卫星定位导航方向的学习者与研究人员,用于解决新版观测文件的数据解析、历元提取与时间转换问题。压缩包共4个文件,包含两个m脚本、一个19…

2026/9/7 16:23:03

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/7 22:46:00

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/9 10:21:54

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码