import type { TangledLocation } from './forge-tangled.js' export interface PageFetchResponse { text(): Promise } export type PageFetch = (url: string) => Promise const PULL_URI = /at:\/\/did:[^/"'<\s]+\/sh\.tangled\.repo\.pull\/[^/"'<\s]+/ const pullPage = (location: TangledLocation, number: number): string => `https://${location.host}/${location.owner}/${location.repo}/pulls/${number}` /** Resolve an appview number only when the page itself names the pull record. Tangled returns its * 404 page with HTTP 200, so the body proof, rather than the response status, is authoritative. */ export async function pullAtNumber( fetchPage: PageFetch, location: TangledLocation, number: number, ): Promise { const body = await (await fetchPage(pullPage(location, number))).text() return PULL_URI.exec(body)?.[0] } /** Find a newly opened pull in the appview's newest-first list, verifying every candidate page. * The cap is deliberately small: a fresh pull is normally the first row, and exhausting the cap is * logged rather than silently turning an incomplete search into an assertion. */ export async function numberForPull( fetchPage: PageFetch, location: TangledLocation, uri: string, options: { limit?: number; log?: (message: string) => void } = {}, ): Promise { const listUrl = `https://${location.host}/${location.owner}/${location.repo}/pulls` const body = await (await fetchPage(listUrl)).text() const numbers: number[] = [] const seen = new Set() const href = /\/pulls\/([1-9][0-9]*)/g for (const match of body.matchAll(href)) { const number = Number(match[1]) if (!seen.has(number)) { seen.add(number) numbers.push(number) } } const limit = options.limit ?? 5 for (const number of numbers.slice(0, limit)) { if ((await pullAtNumber(fetchPage, location, number)) === uri) return pullPage(location, number) } if (numbers.length > limit) { options.log?.(`tangled page-link search for ${uri} exhausted its ${limit}-candidate budget`) } return undefined }