rxdb-devtools
RxDB 与浏览器 DevTools Extension 之间的开发期连接器。它通过当前页面的 window.postMessage 发送事件、数据库摘要、实体查询结果和分支操作结果。
只在开发环境启用
这个包暴露了数据库检查、查询、分支变更和断开能力,禁止在生产构建中初始化。
if (import.meta.env.DEV && typeof window !== 'undefined') {
const { getDevToolsConnector } = await import('@aiao/rxdb-devtools');
getDevToolsConnector().init(rxdb, getEntityMetadata);
}
调用方负责确保生产 bundle 不执行 init()。enabled: false 可用于测试或显式关闭,但不能替代构建期的开发环境门禁。
生命周期
getDevToolsConnector()返回页面级 connector 单例。init(rxdb, getEntityMetadata)注册 RxDB 实例、读取实体 metadata、监听 RxDB 事件并发送HANDSHAKE。- DevTools 返回
HANDSHAKE_ACK后,connector 发送实时事件,并刷新握手前的内存事件缓冲区。 - 对同一个 RxDB 对象重复
init()是幂等操作,不会重复握手、读取 metadata 或注册监听。 - 当前协议明确只支持一个 RxDB 实例。第二个不同实例会在读取其 metadata、修改映射或注册监听前抛错。
disconnect()只断开 connector 通信、清理监听和缓冲区,不调用rxdb.disconnectAll()。DISCONNECT_RXDB或window.__AIAO_RXDB_DEVTOOLS__.disconnectRxdb()会请求关闭 RxDB:graceful:disconnectAll()成功,随后清理实例和监听;forced:graceful 失败,但本地 Worker 终止或 SharedWorker port 关闭成功,随后清理;failed:关闭、超时或强制释放失败,保留实例、监听和全局 helper,允许重试;not-connected:当前没有已注册实例。
SSR
init() 和 disconnect() 在没有 window 的环境中是 no-op。仍建议把动态导入和初始化放在浏览器开发环境分支内,避免服务端无意义加载。
通信与威胁模型
connector 只接受:
event.source === window;event.origin为空或等于当前页面 origin;- 完整且无额外字段的 message envelope;
- 已知消息类型、合法方向和对应命令 payload。
这些检查用于拒绝 malformed 消息,不是身份认证。任何能在同一页面执行 JavaScript 的脚本,都可能构造一条完全合法的 window.postMessage 命令。把 token 放进同一个可观察、可重放的 postMessage 通道只会制造安全幻觉,因此本包不实现这种 token。
真正可信的 capability 需要 Extension 在隔离边界中建立不可由页面脚本伪造的通道;这涉及包外 Extension 协议,当前未实现。在此之前,只能把本包视为开发工具,并把页面上的第三方脚本视为同等可信。
危险命令
DISCONNECT_RXDB:关闭当前 RxDB 实例;QUERY_ENTITY:读取指定实体,limit仅允许缺省或1..1000的安全整数;SWITCH_BRANCH:切换分支;CREATE_BRANCH:创建分支;DELETE_BRANCH:删除分支。
malformed envelope 或 payload 会被静默拒绝,不会进入命令 handler,也不会把非法 limit 回退成默认值。
加密字段策略
getEntityMetadata 返回的 encryptedPropertyMap 是唯一加密字段来源。connector 在初始化时建立字段映射,并执行以下规则:
QUERY_ENTITY的结果中,metadata 声明的顶层字段始终替换为[encrypted];- 事件
entities[].patch、entities[].inversePatch、entities[].data使用同一遮罩规则; - 非敏感字段保持不变;
- 只解释顶层字段名,不解析
profile.ssn一类嵌套路径;嵌套对象中的同名字段不会被递归替换; - 不提供明文 opt-in,DevTools 永远不会通过本协议请求返回 metadata 声明字段的明文;
- serializer 仍会遮罩符合已知加密 envelope 格式的字符串。
如果 metadata 漏报字段,connector 无法猜测其敏感性。实体 metadata 的正确性属于上游安全契约。
bigint / binary wire 表示
QUERY_ENTITY、EVENT、history/change、conflict 和分支响应共用只读 serializer。该表示只服务 DevTools 通信与展示,不用于实体写回或 change 持久化:
type DevToolsBigIntValue = { $rxdb: 1; type: 'bigint'; value: string };
type DevToolsBinaryValue = {
$rxdb: 1;
type: 'binary';
encoding: 'base64url';
value: string;
byteLength: number;
};
- bigint 使用精确十进制字符串;
- binary 复制当前
Uint8Array视图后编码为无 padding 的 base64url; - serializer 不修改实体、patch 或输入字节;
- metadata 声明的加密字段先替换为
[encrypted],再进入 serializer,因此不会暴露明文或 binary 长度; - 面板只识别
$rxdb: 1的合法已知 envelope,其他版本显示unsupported,不会猜测解码。
Fileoverview
RxDB DevTools 集成包 —— 页面侧连接器与 window.postMessage 线协议。
Remarks
入口导出的是协议的完整表面:连接器 + 消息类型 + 类型守卫 + 消息工厂。
DevTools 扩展与本包共用这一套定义,任何一侧自己重写协议形状都会在版本漂移时
静默失配(一侧多一个字段,另一侧的 guard 把整条消息判非法却不报错)。
因此新增消息类型时,types.ts 与本文件必须一并更新。
Classes
| Class | Description |
|---|---|
| DevToolsConnector | RxDB DevTools 连接器。 |
| EventBuffer | 事件缓冲区 在 DevTools 断开连接时缓存事件,重连后 flush |
| SequenceGenerator | 序列号生成器类 生成单调递增的序列号,用于事件排序 |
Interfaces
Type Aliases
Variables
Functions
| Function | Description |
|---|---|
| authorizeMessage | 判断一条消息类型在给定档位下是否可以收发。 |
| authorizeOperation | 对一次操作请求做三层授权。 |
| createConnectorNegotiation | 创建 connector 侧协商机。 |
| createConnectorProviders | 按本页实际具备的能力装配 provider 接缝。 |
| createDevToolsBrowserSettingsProvider | 建一个浏览器 settings provider。 |
| createDevToolsConnectorEndpoint | 创建 connector 侧 v2 端点。 |
| createDevToolsDesktopSettingsDescriptor | 桌面 settings provider 的 descriptor。 |
| createDevToolsDesktopSettingsProvider | 建一个桌面 settings provider。 |
| createDevToolsError | 创建对外错误 envelope。 |
| createDevToolsNativeFilesProvider | 建一个原生文件后端的 files provider。 |
| createDevToolsNativeSnapshotSource | 建一个原生宿主的快照物化来源。 |
| createDevToolsOpfsFilesProvider | 建一个浏览器 OPFS 的 files provider。 |
| createDevToolsPanelEndpoint | 创建 panel 侧数据面客户端。 |
| createDevToolsReadOnlySettingsProvider | 用给定 descriptor 建一个只读 settings provider。 |
| createDevToolsRxdbDatabaseProvider | 建一个 RxDB database provider。 |
| createDevToolsSnapshotStore | 创建一个 session 级快照仓库。 |
| createDevToolsV2Message | 构造一条 v2 消息。 |
| createMessage | 创建 RxDB DevTools 消息。 |
| createPanelNegotiation | 创建 panel 侧协商机。 |
| createProviderError | 按共享的可重试性表构造一个 provider 错误。 |
| createSessionId | 生成 canonical UUID v4 作为 sessionId。 |
| createSystemClock | 基于宿主 Date.now / setTimeout 的真实时钟。 |
| createWindowConnectorTransport | 浏览器实现:window 总线 + MessageChannel 私有端口。 |
| decodeCanonicalBase64 | 解码 RFC 4648 标准 base64,并要求输入是该字节序列的规范编码。 |
| encodeCanonicalBase64 | 把字节序列编码为 RFC 4648 标准 base64(带规范 padding)。 |
| getDevToolsConnector | 获取或创建全局 RxDB DevTools 连接器。 |
| isCanonicalUuidV4 | 判断值是否为 canonical(小写、带连字符)UUID v4。 |
| isControlPlaneErrorCode | 判断值是否为控制面错误码。 |
| isDevToolsCapability | 判断值是否为合法的 capability 档位。 |
| isDevToolsCommandMessage | 判断合法消息是否为允许进入页面处理器的命令。 |
| isDevToolsErrorPayload | 判断值是否为合法的对外错误 envelope。 |
| isDevToolsIdentifier | 判断值是否为合法的 requestId / transferId。 |
| isDevToolsMessage | 严格校验消息 envelope、方向、已知类型和命令 payload。 |
| isDevToolsProviderDescriptor | 判断值是否为合法的 provider descriptor。 |
| isDevToolsProviderDescriptorSet | 判断值是否为合法的 descriptor 集合:每个领域最多一份。 |
| isDevToolsProviderDomain | 判断值是否为已知的 provider 领域。 |
| isDevToolsV2Envelope | 宽外层 guard:判断值是否为一条 v2 消息的外壳,不校验 payload。 |
| isDevToolsV2Message | 严内层 guard:判断值是否为一条完全合法的 v2 消息。 |
| isMaxTransferBytes | 判断值是否为合法的 maxTransferBytes(0~1 GiB 的非负 safe integer)。 |
| isMutatingOperation | 判断一个操作是否会改变持久状态。 |
| isPageSize | 判断值是否为合法的 pageSize(1~500)。 |
| isProviderErrorCode | 判断值是否为 provider 错误码。 |
| isRedactedErrorMessage | 判断值是否为符合脱敏要求的错误说明。 |
| isSupportedVersionList | 判断值是否为合法的 PROTOCOL_HELLO.supportedVersions。 |
| isValidPathSegment | 判断一个路径段是否合法。 |
| isWithinTransferLimit | 判断声明的总字节数是否在协商上限之内。 |
| joinLogicalPath | 把已校验的段拼回逻辑路径。 |
| mapPlatformError | 把一个平台异常映射成 provider 错误载荷。 |
| parseLogicalPath | 把 wire 上的路径切成已校验的段。 |
| resetDevToolsConnector | 重置全局 RxDB DevTools 连接器。 |
| resolveBrowserOpfsRoot | 探测本页是否具备 OPFS。 |
| resolveNegotiatedTransferLimit | 协商出实际生效的 transfer 上限。 |
| satisfiesCapability | 判断实际档位是否达到所需档位。 |
| saveFileThroughPage | 用页面自己的下载路径保存一个文件。 |
| serialize | 序列化 RxDB 事件 |
| serializeDevToolsValue | 将运行时值转换为脱离原对象且 JSON 安全的 DevTools wire 值。 |
| snapshotRecordBytes | 一条规范记录的字节数。 |
| splitLogicalPath | 把路径拆成「父目录段 + 末段」。 |
| totalSnapshotBytes | 一批规范记录的字节总量。 |