SuperAPI AI 工具集

relay-client

编程与开发

查看项目

更新于 10-16 06:06

技能介绍

Relay Client 与订阅

该技能描述 Amethyst 中 relay 客户端层(位于 commons/.../relayClient/)的订阅与过滤器组装模式,是 LocalCache/Account 与原始 relay 连接之间的中间层。它确保 composable 只订阅可见内容,跨屏幕去重过滤器,并对“为这 200 个 pubkey 获取元数据”之类的批量查询进行限速。

何时使用

新增需要尚未拥有事件的屏幕(编写 FilterAssembler);将 composable 接入进入时订阅、离开时取消订阅(ComposeSubscriptionManager);为一组 pubkey 预加载元数据或头像(MetadataPreloader);跨并发屏幕去重相同过滤器;处理 EOSE → “已有历史数据,停止显示加载中”的过渡。

目录布局

全部位于 commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/ 下:assemblers/(根据输入构建 relay Filter,如 MetadataFilterAssembler、ReactionsFilterAssembler、FeedMetadataCoordinator、CashuMintDirectoryFilterAssembler、CashuWalletFilterAssembler)、composeSubscriptionManagers/(ComposeSubscriptionManager 接口 Subscribable<T>、MutableComposeSubscriptionManager 参考实现、ComposeSubscriptionManagerControls)、eoseManagers/(按订阅跟踪 EOSE,含 IEoseManager / BaseEoseManager / PerKeyEoseManager / SingleSubEoseManager)、nip17Dm/(gift-wrap 私信管线,含 FilterGiftWrapsToPubkey、GiftWrapDecryptor)、preload/(MetadataPreloader 带限速的批量元数据获取、MetadataRateLimiter 令牌桶式限速器)、subscriptions/(KeyDataSourceSubscription、LifecycleAwareKeyDataSourceSubscription、PrioritizedSubscriptionQueue、SubscriptionPriority)。

核心概念:Subscribable<T>

接口定义包含 state: StateFlow<T>、subscribe()、unsubscribe()。每个功能级管理器实现或内嵌 Subscribable。MutableComposeSubscriptionManager 参考实现使用引用计数,使两个屏幕请求同一 feed 时共享一个订阅,只有最后一个离开者才真正关闭。ComposeSubscriptionManagerControls 提供 DisposableEffect 风格辅助方法,避免 composable 在导航离开或进程后台化时泄漏订阅。

典型流程

在 composable 中通过 rememberSubscribable 创建订阅,用 LaunchedEffect 调用 subscribe(),用 DisposableEffect 的 onDispose 调用 unsubscribe(),再通过 state.collectAsStateWithLifecycle() 渲染。assembler 生成 Filter(参见 quartz 模块的 RelayFilters.kt)。底层 RelayPool 负责去重、打开订阅、将事件发送到 LocalCache.consume,并通过 eose manager 发出 EOSE。

Assemblers

assembler 是普通类,保持纯净——无状态、无 I/O。它是组合的接缝:FeedMetadataCoordinator 接收可见 note 列表,组装覆盖所有被引用 pubkey 的单个元数据过滤器。

按可见加载——规范入口点(observeUser* / observeNote*)

优先使用这些入口,而非手写的“为这个列表加载元数据”调用。它们是共享的 KMP 方式,只为屏幕上的内容加载数据:composable 在组合期间订阅,离开后(或应用后台化)约 30 秒取消订阅。两者都位于 commons/relayClient/:按用户(relayClient/user/)observeUserInfo/Picture/Banner/AboutMe/Name(user),各自打开组合作用域的 UserFinderFilterAssemblerSubscription(user) 并返回响应式 State;元数据(kind 0 + relay 列表)只为屏幕上用户加载,每个 relay 合并为一个批量 REQ 请求整个可见集。按 note(relayClient/event/)EventFinderFilterAssemblerSubscription(note) 在组合期间加载 note 的交互(reactions / zaps / reposts / replies);Android 的 observeNote* 显示观察者建立于同一订阅之上。两者读取前端提供的 CompositionLocals——LocalUserFinder / LocalUserFinderAccount(被 event finder 复用)/ LocalEventFinder——在组合根附近提供一次(Android AppModules,Desktop Main.kt 通过其订阅协调器)。account 接缝是窄接口 UserFinderAccount(快照 relay-hint getter),而非臃肿的 IAccount。error() 默认值意味着这些绝不能在没有 relay 客户端的组合中访问(例如 Android :napplet 沙箱)。load-once、viewport-batch 路径(FeedMetadataCoordinator.loadMetadataForNotes / loadMetadataBatched)在前台加载中已被取代;MetadataPreloader 仅作为可选的后台离屏预热器保留。

Preloaders

MetadataPreloader 是“我需要 200 个 pubkey 的元数据,但不想烧掉 CPU 或 relay”的路径。它使用 MetadataRateLimiter(令牌桶)限流批量获取,并分组为 relay 友好的块。相关地:amethyst/.../service/images/ImageLoaderSetup.kt 也使用预加载器进行 blurhash 水合——它们是一种通用模式,并非元数据专用。

EOSE 处理

每个订阅按 relay 跟踪“存储事件结束”(End of Stored Events)。eoseManagers/ 中的 eose manager 将各 relay 的 EOSE 聚合为单个“加载完成”布尔值,供 UI 隐藏加载指示器。若不聚合,composable 会在各 relay 逐个确认时闪烁。

模式

应当:为每个功能作用域(屏幕 / 对话框 / 卡片)构建一个 Subscribable;通过引用计数去重——多个相同订阅应共享;使用 DisposableEffect / LaunchedEffect 将订阅/取消订阅与生命周期绑定;将 relay Filter 构建放在 assembler 中,使测试变得简单;通过 MetadataPreloader 路由批量元数据,不要发起 N 个订阅。不应:在 composable 中直接调用 RelayPool / NostrClient——始终通过 Subscribable;不要在 composable 生命周期之后持有订阅——会造成内存和 socket 泄漏;不要在 composable 内联构建临时过滤器——只用 assembler;不要为所有内容预加载元数据——它是受限资源,会与用户可见加载竞争。

相关

无头 / 一次性客户端操作(CLI、geode、测试、非 compose 代码):不要走 Subscribable——使用 quartz/…/nip01Core/relay/client/accessories/ 中的 INostrClient 扩展函数(fetchAll、fetchFirst、fetchAllPages、publishAndConfirm、count、negentropyReconcile/negentropySync、…)。它们是扩展,不会出现在“NostrClient 的用法”下——在编写原始 subscribe/collect 循环前,请查看该包的 README.md 目录。另参见 nostr-expert/references/tag-patterns.md(标签如何影响过滤器需要查找的内容)、kotlin-coroutines/references/relay-patterns.md(relay 池内部,assembler 之下的同级层)、feed-patterns 技能(feed 组合多个 Subscribable:内容 + 元数据 + reactions)、account-state 技能(Account 的按 kind 流本身就是 relay-client 层的消费者)。