(link: string, baseURL: string)
| 85 | } |
| 86 | |
| 87 | function parseLinkHeaderURL(link: string, baseURL: string): URL | null { |
| 88 | for (const linkValue of splitLinkHeaderValues(link)) { |
| 89 | const targetStart = linkValue.indexOf("<"); |
| 90 | const targetEnd = linkValue.indexOf(">", targetStart + 1); |
| 91 | if (targetStart === -1 || targetEnd === -1) { |
| 92 | continue; |
| 93 | } |
| 94 | |
| 95 | const params = linkValue |
| 96 | .slice(targetEnd + 1) |
| 97 | .split(";") |
| 98 | .map((param) => param.trim()) |
| 99 | .filter((param) => param.length > 0); |
| 100 | const isNext = params.some((param) => { |
| 101 | const [name, ...valueParts] = param.split("="); |
| 102 | if (name.toLowerCase() !== "rel" || valueParts.length === 0) { |
| 103 | return false; |
| 104 | } |
| 105 | |
| 106 | const value = valueParts.join("=").trim().replace(/^"|"$/g, ""); |
| 107 | return value.split(/\s+/).includes("next"); |
| 108 | }); |
| 109 | if (!isNext) { |
| 110 | continue; |
| 111 | } |
| 112 | |
| 113 | const href = linkValue.slice(targetStart + 1, targetEnd).trim(); |
| 114 | try { |
| 115 | return new URL(href); |
| 116 | } catch { |
| 117 | if (baseURL.length === 0) { |
| 118 | return null; |
| 119 | } |
| 120 | |
| 121 | try { |
| 122 | return new URL(href, baseURL); |
| 123 | } catch { |
| 124 | return null; |
| 125 | } |
| 126 | } |
| 127 | } |
| 128 | |
| 129 | return null; |
| 130 | } |
| 131 | |
| 132 | function isOpaqueReferrersCursor(cursor: string): boolean { |
| 133 | return cursor.startsWith("/v2/"); |
no test coverage detected