main
ts 105 lines 2.74 KB
Raw
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 }