| 1 | import { BROWSER_API_PATHS } from "./apiPaths.js"; |
| 2 | |
| 3 | interface Metadata { |
| 4 | description: string; |
| 5 | tags: string[]; |
| 6 | thumbnail: string; |
| 7 | owner: string; |
| 8 | } |
| 9 | |
| 10 | export interface PaymentDisplay { |
| 11 | enabled: boolean; |
| 12 | label: string; |
| 13 | } |
| 14 | |
| 15 | const EMPTY_METADATA: Metadata = { |
| 16 | description: "", |
| 17 | tags: [], |
| 18 | thumbnail: "", |
| 19 | owner: "", |
| 20 | }; |
| 21 | |
| 22 | function isRecord(value: unknown): value is Record<string, unknown> { |
| 23 | return typeof value === "object" && value !== null && !Array.isArray(value); |
| 24 | } |
| 25 | |
| 26 | function metadataFromRecord(value: Record<string, unknown>): Metadata { |
| 27 | return { |
| 28 | description: typeof value.description === "string" ? value.description : "", |
| 29 | tags: Array.isArray(value.tags) |
| 30 | ? value.tags |
| 31 | .map((tag) => (typeof tag === "string" ? tag.trim() : "")) |
| 32 | .filter(Boolean) |
| 33 | : [], |
| 34 | thumbnail: typeof value.thumbnail === "string" ? value.thumbnail : "", |
| 35 | owner: typeof value.owner === "string" ? value.owner : "", |
| 36 | }; |
| 37 | } |
| 38 | |
| 39 | export function parseLeaseMetadata(metadataValue: unknown): Metadata { |
| 40 | if (!metadataValue) { |
| 41 | return EMPTY_METADATA; |
| 42 | } |
| 43 | |
| 44 | if (isRecord(metadataValue)) { |
| 45 | return metadataFromRecord(metadataValue); |
| 46 | } |
| 47 | |
| 48 | if (typeof metadataValue !== "string") { |
| 49 | return EMPTY_METADATA; |
| 50 | } |
| 51 | |
| 52 | try { |
| 53 | const parsed = JSON.parse(metadataValue); |
| 54 | if (!isRecord(parsed)) { |
| 55 | return EMPTY_METADATA; |
| 56 | } |
| 57 | |
| 58 | return metadataFromRecord(parsed); |
| 59 | } catch { |
| 60 | return EMPTY_METADATA; |
| 61 | } |
| 62 | } |
| 63 | |
| 64 | export function resolveLeaseThumbnail(metadata: Metadata, hostname: string): string { |
| 65 | const configuredThumbnail = metadata.thumbnail.trim(); |
| 66 | if (configuredThumbnail !== "") { |
| 67 | return configuredThumbnail; |
| 68 | } |
| 69 | |
| 70 | const normalizedHostname = hostname.trim().toLowerCase().replace(/\.$/, ""); |
| 71 | if (normalizedHostname === "" || normalizedHostname.startsWith("*.")) { |
| 72 | return ""; |
| 73 | } |
| 74 | return `${BROWSER_API_PATHS.thumbnail.prefix}${encodeURIComponent(normalizedHostname)}`; |
| 75 | } |
| 76 | |
| 77 | export function resolveLeasePayment(metadata: Metadata): PaymentDisplay { |
| 78 | const normalizedTags = new Set( |
| 79 | metadata.tags.map((tag) => tag.trim().toLowerCase()).filter(Boolean) |
| 80 | ); |
| 81 | const description = metadata.description.toLowerCase(); |
| 82 | const enabled = |
| 83 | normalizedTags.has("x402") || |
| 84 | normalizedTags.has("payment") || |
| 85 | normalizedTags.has("paid") || |
| 86 | normalizedTags.has("usdc") || |
| 87 | /\bx402\b|\bpaid\b|\bpayment\b|\busdc\b/.test(description); |
| 88 | |
| 89 | if (!enabled) { |
| 90 | return { enabled: false, label: "" }; |
| 91 | } |
| 92 | |
| 93 | const labelParts: string[] = []; |
| 94 | if (normalizedTags.has("x402") || description.includes("x402")) { |
| 95 | labelParts.push("x402"); |
| 96 | } |
| 97 | if (normalizedTags.has("usdc") || description.includes("usdc")) { |
| 98 | labelParts.push("USDC"); |
| 99 | } |
| 100 | |
| 101 | return { |
| 102 | enabled: true, |
| 103 | label: labelParts.length > 0 ? labelParts.join(" ") : "Paid app", |
| 104 | }; |
| 105 | } |