UNPKG

bigblocks

Version:

Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React

224 lines (223 loc) 7.63 kB
import { PROTOCOL_IDS } from "../../social/types/protocol.js"; // Protocol prefixes for MetaLens export const METALENS_PROTOCOL = { APP: "metalens", VERSION: "1.0.0", }; /** * Build MAP protocol data for MetaLens comments */ export function buildMetaLensMapData(params) { const mapData = { app: METALENS_PROTOCOL.APP, type: params.parentId ? "reply" : "comment", context: params.context, [params.context]: params.value, timestamp: new Date().toISOString(), }; // Add subcontext if provided if (params.subcontext && params.subcontextValue) { mapData.subcontext = params.subcontext; mapData[params.subcontext] = params.subcontextValue; } // Add parent reference for replies if (params.parentId) { mapData.thread = params.parentId; } // Add tags if provided if (params.tags && params.tags.length > 0) { mapData.tags = params.tags.join(","); } // Add mentions if provided if (params.mentions && params.mentions.length > 0) { mapData.mentions = params.mentions.join(","); } return mapData; } /** * Build a complete MetaLens comment transaction */ export function buildMetaLensTransaction(params) { const mapData = buildMetaLensMapData(params); // Build protocol data array // Format: [B protocol data, MAP protocol data, AIP will be added by signing] const protocolData = []; // B protocol for content protocolData.push(PROTOCOL_IDS.B, // B prefix params.content, params.contentType || "text/plain", "utf8"); // MAP protocol for metadata protocolData.push(PROTOCOL_IDS.MAP, // MAP prefix "SET"); // Add all MAP data as key-value pairs for (const [key, value] of Object.entries(mapData)) { protocolData.push(key, value); } return protocolData; } /** * Parse MetaLens comment from transaction data */ export function parseMetaLensComment(data) { try { // Extract MAP data const mapData = data.MAP?.[0] || {}; // Extract content from B protocol const content = data.B?.[0]?.content || ""; const contentType = data.B?.[0]?.["content-type"] || "text/plain"; // Extract author from AIP (for properly signed transactions) const aipData = data.AIP?.[0] || {}; const author = aipData.address || "Anonymous"; // For now, accept all social posts as potential MetaLens comments // Later we'll filter for proper MetaLens protocol data const isMetaLensComment = mapData.app === METALENS_PROTOCOL.APP; // If it's a proper MetaLens comment, parse the context let contextType = "custom"; let contextValue = ""; if (isMetaLensComment) { contextType = mapData.context; contextValue = mapData[contextType] || ""; } else { // For legacy posts, try to infer context from content // This is a fallback until we have proper MetaLens protocol adoption if (content.includes("http://") || content.includes("https://")) { contextType = "url"; const urlMatch = content.match(/(https?:\/\/[^\s]+)/); contextValue = urlMatch?.[1] || content; } else { contextType = "custom"; contextValue = content.substring(0, 50); // Use first 50 chars as context } } // Parse timestamp const timestamp = mapData.timestamp ? new Date(mapData.timestamp) : new Date(data.timestamp || Date.now()); // Build comment object const comment = { txid: data.tx?.h || "", author, content, contentType: contentType, timestamp, context: { type: contextType, value: contextValue, subcontext: mapData.subcontext, subcontextValue: mapData.subcontext ? mapData[mapData.subcontext] : undefined, }, metadata: { app: mapData.app || "unknown", thread: mapData.thread, tags: mapData.tags?.split(",").filter(Boolean), mentions: mapData.mentions?.split(",").filter(Boolean), }, }; return comment; } catch (error) { console.error("Failed to parse MetaLens comment:", error); return null; } } /** * Build EventSource URL for real-time updates */ export function buildMetaLensStreamUrl(apiUrl, context, value) { const params = new URLSearchParams({ context, value, app: METALENS_PROTOCOL.APP, }); return `${apiUrl}/stream/comments?${params.toString()}`; } /** * Validate context type and value */ export function validateContext(type, value) { if (!value || typeof value !== "string") { return { valid: false, error: "Context value is required" }; } switch (type) { case "tx": // Validate transaction ID (64 hex chars) if (!/^[a-fA-F0-9]{64}$/.test(value)) { return { valid: false, error: "Invalid transaction ID format" }; } break; case "upc": // Validate UPC (12-14 digits) if (!/^\d{12,14}$/.test(value)) { return { valid: false, error: "Invalid UPC format" }; } break; case "url": // Validate URL try { new URL(value); } catch { return { valid: false, error: "Invalid URL format" }; } break; case "geohash": // Basic geohash validation (alphanumeric, 4-12 chars) if (!/^[0-9a-z]{4,12}$/.test(value)) { return { valid: false, error: "Invalid geohash format" }; } break; case "ipfs": // IPFS hash validation if (!value.startsWith("Qm") && !value.startsWith("ipfs://")) { return { valid: false, error: "Invalid IPFS hash format" }; } break; case "isbn": { // ISBN validation (10 or 13 digits) const cleanIsbn = value.replace(/-/g, ""); if (!/^(?:\d{9}[\dX]|\d{13})$/.test(cleanIsbn)) { return { valid: false, error: "Invalid ISBN format" }; } break; } case "doi": // DOI validation if (!/^10\.\d{4,}\/[-._;()\/:a-zA-Z0-9]+$/.test(value)) { return { valid: false, error: "Invalid DOI format" }; } break; } return { valid: true }; } /** * Format context for display */ export function formatContextDisplay(type, value) { switch (type) { case "url": try { const url = new URL(value); return url.hostname + (url.pathname !== "/" ? url.pathname : ""); } catch { return value; } case "tx": return `${value.substring(0, 8)}...${value.substring(56)}`; case "upc": return `Product ${value}`; case "geohash": return `Location ${value.substring(0, 6)}`; case "isbn": return `Book ISBN ${value}`; case "doi": return `Paper ${value}`; case "ipfs": return `IPFS ${value.substring(0, 12)}...`; default: return value; } }