* Mirror of the desktop `get_thread_replies` command: return the full reply * subtree under a root, chronological (oldest first), excluding the root itself, * with gap-free `(created_at, event_id)` keyset paging. * * The event-id tiebreak is load-bearing — same-second replies must all page * th
(
args: {
rootEventId: string;
channelId?: string | null;
limit?: number | null;
depthLimit?: number | null;
cursor?: RawThreadCursor | null;
},
config: E2eConfig | undefined,
)
| 3469 | * assert the paged union equals the whole subtree, matching the relay contract. |
| 3470 | */ |
| 3471 | async function handleGetThreadReplies( |
| 3472 | args: { |
| 3473 | rootEventId: string; |
| 3474 | channelId?: string | null; |
| 3475 | limit?: number | null; |
| 3476 | depthLimit?: number | null; |
| 3477 | cursor?: RawThreadCursor | null; |
| 3478 | }, |
| 3479 | config: E2eConfig | undefined, |
| 3480 | ): Promise<RawThreadRepliesResponse> { |
| 3481 | const cap = Math.min(args.limit ?? 200, 500); |
| 3482 | const filter: MockFilter & Record<string, unknown> = { |
| 3483 | "#e": [args.rootEventId], |
| 3484 | depth_limit: args.depthLimit ?? 64, |
| 3485 | kinds: [...TIMELINE_KINDS], |
| 3486 | limit: cap, |
| 3487 | }; |
| 3488 | if (args.channelId) { |
| 3489 | filter["#h"] = [args.channelId]; |
| 3490 | } |
| 3491 | if (args.cursor) { |
| 3492 | filter.thread_cursor = args.cursor.created_at; |
| 3493 | filter.thread_cursor_id = args.cursor.event_id; |
| 3494 | } |
| 3495 | const identity = getIdentity(config); |
| 3496 | if ( |
| 3497 | !isPGatedFilterAuthorized( |
| 3498 | filter, |
| 3499 | identity?.pubkey ?? getMockMemberPubkey(config), |
| 3500 | ) |
| 3501 | ) { |
| 3502 | throw new Error(P_GATED_REJECTION_MESSAGE); |
| 3503 | } |
| 3504 | |
| 3505 | let subtree: RelayEvent[]; |
| 3506 | if (!identity) { |
| 3507 | // Mock store: walk the reply forest transitively from the root so nested |
| 3508 | // replies (reply-to-a-reply) are included, matching thread_metadata depth. |
| 3509 | const events = args.channelId |
| 3510 | ? getMockMessageStore(args.channelId) |
| 3511 | : Array.from(mockMessages.values()).flat(); |
| 3512 | const byId = new Map(events.map((event) => [event.id, event])); |
| 3513 | const root = byId.get(args.rootEventId); |
| 3514 | const collected: RelayEvent[] = []; |
| 3515 | const included = new Set<string>(); |
| 3516 | if (!root) { |
| 3517 | subtree = collected; |
| 3518 | } else { |
| 3519 | const frontier = new Set<string>([root.id]); |
| 3520 | for (;;) { |
| 3521 | let added = false; |
| 3522 | for (const event of events) { |
| 3523 | if (included.has(event.id)) { |
| 3524 | continue; |
| 3525 | } |
| 3526 | const ref = getThreadReferenceFromTags(event.tags); |
| 3527 | if (!ref.parentEventId || !frontier.has(ref.parentEventId)) { |
| 3528 | continue; |
no test coverage detected