web3-providers-ws 4.x 演进全解:从 changelog 看 WebSocket 提供者的架构重构、构建体系与稳定化之路
【免费下载链接】web3.jsCollection of comprehensive TypeScript libraries for Interaction with the Ethereum JSON RPC API and utility functions.项目地址: https://gitcode.com/gh_mirrors/we/web3.js
web3-providers-ws是 web3.js 4.x 生态中专用于 WebSocket 协议的 provider 子包,负责以ws:///wss://长连接方式与 Ethereum 节点通信,支撑实时事件订阅等场景。本文以该包自 4.0.1-alpha 至 4.0.8 的 CHANGELOG.md 为骨架,逐条解读每个版本的关键变更,并结合仓库源码深入剖析SocketProvider抽象、重连机制、ESM/CJS 混合构建与消息分块解析等底层实现,帮助读者既看懂版本演进脉络,也掌握WebSocketProvider的实际用法与原理。
版本脉络总览:一条从 alpha 走向稳定的演进路线
回顾整个 changelog,web3-providers-ws的 4.x 早期版本大体可分为三个阶段:
| 阶段 | 版本区间 | 主题 |
|---|---|---|
| 依赖同步期 | 4.0.1-alpha.2 / alpha.3 / alpha.5 | 跟随 web3.js 主仓更新依赖 |
| 架构重构期 | 4.0.1-alpha.4 | 迁移到公共SocketProvider抽象类,废弃close事件 |
| 发布体系成型期 | 4.0.1-rc.0 ~ 4.0.1 | 命名导出、SocketConnectiongetter、源文件发布、ESM/CJS 混合构建 |
| 稳定性治理期 | 4.0.2 ~ 4.0.8 | 修复@types/ws问题、固定类型依赖版本、修复分块处理 bug |
其中4.0.1是首个稳定(非预发布)版本,其 Release Notes 明确说明详细的变更日志均列于此前各 alpha 与 RC 版本中,因此 4.0.x 全系的核心能力在 4.0.1 已定型,后续版本以修复与依赖治理为主。
架构重构:统一抽象类 SocketProvider(4.0.1-alpha.4, #5683)
changelog 在4.0.1-alpha.4中记录了两项关键变更:
main与files字段由dist/改为lib/目录(#5739)- 重构为使用公共
SocketProvider类(#5683) - 旧事件
close被废弃,由disconnect取代(#5683)
其中第二条是架构层面的核心动作。重构后,WebSocketProvider不再自行管理底层连接细节,而是继承位于 socket_provider.ts 的抽象类SocketProvider。从源码可见该抽象类的职责划分:
- 连接生命周期:
connect()、disconnect()、safeDisconnect()、reset() - 请求队列管理:
_pendingRequestsQueue与_sentRequestsQueue两张Map,分别存放连接建立前与已发送的请求 - 事件体系:
on/once/removeListener/removeAllListeners - 重连逻辑:
_reconnect()与ReconnectOptions - 消息解析:通过
ChunkResponseParser处理分块响应
WebSocketProvider只需实现抽象方法即可完成一个完整 provider,见 src/index.ts:
protected _openSocketConnection() { this._socketConnection = new WebSocket( this._socketPath, undefined, this._socketOptions && Object.keys(this._socketOptions).length === 0 ? undefined : this._socketOptions, ); } protected _sendToSocket<Method extends Web3APIMethod<API>>(payload): void { if (this.getStatus() === 'disconnected') { throw new ConnectionNotOpenError(); } this._socketConnection?.send(JSON.stringify(payload)); } protected _parseResponses(event: WebSocket.MessageEvent) { return this.chunkResponseParser.parseResponse(event.data as string); }这一抽象的价值在于:IpcProvider(web3-providers-ipc)与WebSocketProvider共享了全部队列、重连、事件逻辑,两个包的行为保持一致,也方便未来接入其他 socket 协议。
close → disconnect 事件迁移
同一次重构中,旧的事件名close被废弃,统一为disconnect。新的事件体系由SocketProvider提供:connect、disconnect、message、chainChanged、accountsChanged等。例如监听断开事件:
provider.on('disconnect', error => { console.log('连接已断开', error); });集成测试 reconnection.test.ts 中即通过waitForEvent(web3Provider, 'connect')等待连接建立,验证了这套新事件体系的可用性。
重连机制与 ReconnectOptions:默认值与自定义策略
SocketProvider在构造函数中将用户传入的reconnectOptions与默认值合并:
export type ReconnectOptions = { autoReconnect: boolean; delay: number; maxAttempts: number; }; const DEFAULT_RECONNECTION_OPTIONS = { autoReconnect: true, delay: 5000, maxAttempts: 5, };也就是说,默认开启自动重连、间隔 5 秒、最多尝试 5 次。集成测试明确断言了这三个默认值,见 reconnection.test.ts:
expect(web3Provider._reconnectOptions).toEqual({ autoReconnect: true, delay: 5000, maxAttempts: 5, });自定义重连策略时,只需传入第三个构造参数:
const provider = new WebSocketProvider( 'wss://mainnet.infura.io/ws/v3/YOUR_INFURA_ID', {}, { delay: 500, autoReconnect: true, maxAttempts: 10, }, );从_reconnect()的实现(socket_provider.ts)可以看到几个关键细节:
- 重连时会对
_sentRequestsQueue中所有请求以PendingRequestsOnReconnectingError拒绝,避免请求悬挂; - 若重连次数未达
maxAttempts,则延迟delay毫秒后重连;超过则清空队列并以MaxAttemptsReachedOnReconnectingError触发error事件; _onCloseEvent(src/index.ts)中,仅当autoReconnect开启且关闭码不在[1000, 1001](正常关闭码)或!event.wasClean时才触发重连,避免对正常关闭反复重连。
disconnect()默认使用关闭码1000(NORMAL_CLOSE_CODE),safeDisconnect()则先等待待处理与已发送队列清空再断开,适合需要优雅收尾的场景。
构建与发布体系演进:dist → lib 与 ESM/CJS 混合构建
changelog 中关于构建发布的变更链条非常清晰:
- 4.0.1-alpha.4(#5739):
main/files从dist/改为lib/ - 4.0.1-rc.1(#5904):新增 ESM 与 CJS 的混合构建
- 4.0.1-rc.1(#5956):发布内容中加入源文件
当前 package.json 印证了这套体系:
{ "name": "web3-providers-ws", "version": "4.0.8", "main": "./lib/commonjs/index.js", "module": "./lib/esm/index.js", "exports": { ".": { "types": "./lib/types/index.d.ts", "import": "./lib/esm/index.js", "require": "./lib/commonjs/index.js" } }, "files": ["lib/**/*", "src/**/*"], "engines": { "node": ">=14", "npm": ">=6.12.0" } }exports字段按import/require分别指向 ESM 与 CJS 产物,types指向类型声明,同时保留main/module兼容旧工具链。构建脚本则通过tsc --build分别产出三种目标:tsconfig.cjs.json、tsconfig.esm.json、tsconfig.types.json,并在各自目录写入{"type": "commonjs"}/{"type": "module"}的package.json以明确模块类型。
依赖方面,运行时依赖收敛为isomorphic-ws(跨 Node/浏览器环境的 WebSocket 封装)与ws,类型依赖固定为@types/ws@8.5.3,其余为 web3.js 内部包(web3-errors、web3-types、web3-utils)。engines声明支持 Node.js >= 14,ES 目标为 2020。
API 表面演化:命名导出与 SocketConnection getter
命名导出 WebSocketProvider(4.0.1-rc.0, #5771)
WebSocketProvider既以export default提供默认导出,也通过export { WebSocketProvider }提供命名导出(见 src/index.ts 末尾)。同时该包还转发了ClientRequestArgs(来自http)与ClientOptions(来自isomorphic-ws)两个类型,方便使用者构造 socket 选项时获得类型提示。
web3主包也对外统一导出了该 provider(packages/web3/src/index.ts),因此可以直接:
import { Web3, WebSocketProvider } from 'web3'; const web3 = new Web3(new WebSocketProvider('wss://mainnet.infura.io/ws/v3/YOUR_INFURA_ID'));SocketConnection getter 返回 isomorphic WebSocket(4.0.1-rc.0, #5891)
SocketProvider提供了SocketConnectiongetter(socket_provider.ts),WebSocketProvider中其返回类型为 isomorphic 的WebSocket。这意味着你可以绕过 provider 抽象,直接访问底层 socket 的特殊属性,或注册自定义的服务器事件监听。单元测试也验证了这一点:
expect(wsProvider.SocketConnection).toBeInstanceOf(WebSocket);稳定性治理:类型依赖修复与分块解析 bug
进入 4.0.2 之后,changelog 的主题转向修复与依赖治理:
- 4.0.2(#6205):修复
#6162的@types/ws问题 - 4.0.4(#6309):将
@types/ws固定为8.5.3,杜绝类型包版本漂移引发的编译问题 - 4.0.7(#6496):修复 chunks 处理逻辑中的 bug
- 其余版本以依赖更新为主
其中 #6496 对应的正是ChunkResponseParser(chunk_response_parser.ts)的分块去重逻辑。WebSocket 消息可能被 TCP 层拆分成多个 chunk,也可能多个 JSON-RPC 响应粘合在一条消息中,解析器通过正则将相邻的 JSON 边界切分(}|--|{、}]|--|[{等模式),再对每个片段尝试JSON.parse:解析失败则缓存为lastChunk,等待下一个消息拼接后继续解析,同时设置 15 秒超时,超时后若未开启自动重连则清空队列并抛出InvalidResponseError。
从 changelog 到实践:WebSocketProvider 完整使用指南
安装
npm install web3-providers-ws # 或 yarn add web3-providers-ws初始化与验证
WebSocketProvider的构造签名(src/index.ts)为:
constructor( socketPath: string, socketOptions?: ClientOptions | ClientRequestArgs, reconnectOptions?: Partial<ReconnectOptions>, )socketPath:必须为ws://或wss://开头的字符串,否则构造时抛出InvalidClientError。合法/非法地址的样例可见 test_data.ts(validConnectionStrings/invalidConnectionStrings)。socketOptions:透传给 isomorphic-ws 的ClientOptions或 Node 的ClientRequestArgs,例如设置headers携带 API key、handshakeTimeout、perMessageDeflate等。注意源码中若传入空对象{}会被视为未传而忽略。reconnectOptions:如上文所述的重连参数,可选。
官方 provider 指南 02_web3_providers_guide/index.md 给出了两种典型写法:
// 方式一:同时配置 socket 选项与重连选项 const provider = new WebSocketProvider( 'ws://localhost:8545', { headers: { // 节点服务要求 API key 放在请求头中时 'x-api-key': '<API key>', }, }, { delay: 500, autoReconnect: true, maxAttempts: 10, }, ); // 方式二:仅配置重连选项 const provider = new WebSocketProvider( 'ws://localhost:8545', {}, { delay: 500, autoReconnect: true, maxAttempts: 10, }, );发起请求与订阅
provider 通过 EIP-1193 风格的request()发送 JSON-RPC 请求(socket_provider.ts):连接中时请求进入_sentRequestsQueue并立即发送;连接尚未建立时进入_pendingRequestsQueue,待open事件触发后统一补发(_sendPendingRequests)。同时supportsSubscriptions()恒返回true,配合 web3.js 的事件订阅体系可实现eth_subscribe等实时订阅。
单元测试 web_socket_provider.test.ts 演示了最小请求流程:
const wsProvider = new WebSocketProvider('ws://localhost:8545'); const jsonRpcPayload = { jsonrpc: '2.0', id: 42, method: 'eth_getBalance', params: ['0x407d73d8a49eeb85d32cf465507dd71d507100c1', 'latest'], }; const result = await wsProvider.request(jsonRpcPayload);断开连接与程序退出
WebSocket 是常驻长连接,进程不会自动退出。需要显式断开:
// 直接断开(默认关闭码 1000) web3.currentProvider?.disconnect(); // 或等待请求队列清空后优雅断开 await provider.safeDisconnect();测试与质量保障
web3-providers-ws的测试分为单元与集成两层(见 test 目录):
- 单元测试web_socket_provider.test.ts:mock
isomorphic-ws,覆盖构造合法性校验(合法/非法 URL 断言)、provider 方法集(request、getStatus、supportsSubscriptions、on、connect、disconnect等)、选项传入与request成功路径; - 集成测试reconnection.test.ts:针对 Node 环境,验证默认/自定义重连选项、
connect/disconnect事件发射、_reconnectOptions的合并结果;geth_fault_tolerance.test.ts 则模拟节点故障场景验证容错能力。
运行方式(见 package.json scripts):
npm run test:unit npm run test:integration结语:从 changelog 反推 4.x 的设计取舍
透过这份 changelog 可以清晰看到web3-providers-ws的演进思路:先用SocketProvider抽象统一 socket 类 provider 的公共能力并重塑事件体系,再完善发布形态(lib/目录、ESM/CJS/types 三产物 + 源文件),随后通过固定@types/ws版本、修复分块解析 bug 等举措走向稳定。对于使用者而言,理解这条脉络意味着:升级 4.x 时优先关注close→disconnect的事件迁移与构造参数行为,遇到类型问题检查@types/ws是否被固定为8.5.3,而在生产环境中应显式配置ReconnectOptions以匹配业务对连接可用性的要求。
【免费下载链接】web3.jsCollection of comprehensive TypeScript libraries for Interaction with the Ethereum JSON RPC API and utility functions.项目地址: https://gitcode.com/gh_mirrors/we/web3.js
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考